RIA Services and WIF – Part II


As promised in my previous post, here’s the second part of my findings playing with WIF and RIA Services beta. This time, I used the HRApp sample available here.

The initial steps are essentially the same I described before:

1- Installed the sample and make sure it runs

2- Ran “FedUtil” which:

a- Created an STS and added it to the solution.

b- Change the application web.config with necessary settings for WIF (added modules, added federation information, etc)

3- F5 and voila. It just works:

image

 

image

 

My next step was to make a few modifications to:

 

1- Transfer “Roles” information to the UI. I  wanted that information to be available on the Silverlight app and eventually drive UI behavior based on that.

2- Add finer grained Authorization logic to the service. For example, I wanted not only authenticated users to approve sabbaticals, but only those who are “Managers”

 

More good news! It turns out this is very easy to implement:

For #1, I simply used the same technique I used in my original prototype (adding a RoleProvider that inspects the Role claims):

 

public override string[] GetRolesForUser(string username)
{
    var id = HttpContext.Current.User.Identity as IClaimsIdentity;
    return (from c in id.Claims
            where c.ClaimType == ClaimTypes.Role
            select c.Value).ToArray();
}

 

Note: in RIA Services beta, “RiaContext” has been replaced by “WebContext”. So instead of using: RiaContext.Current.User.Roles you use: WebContext.Current.User.Roles.

 

The second requirement is even easier. RIA Services ships with a built in attribute you can use to decorate a method in your service: RequiresRoleAttribute.

So, I simply replaced “RequiresAuthentication” in the original sample for:

 

[RequiresRole("Manager")]
public void ApproveSabbatical(Employee current)
{
    // Start custom workflow here
    if (current.EntityState == EntityState.Detached)
    {
        this.ObjectContext.Attach(current);
    }
    current.CurrentFlag = false;
}

 

The STS that FedUtil created for me, adds this claim to the token when I authenticate (see inside GetOutputIdentityClaims method in CustomSecurityTokenService.cs):

    ClaimsIdentity outputIdentity = new ClaimsIdentity();

    // Issue custom claims.
    // TODO: Change the claims below to issue custom claims required by your application.
    // Update the application's configuration file too to reflect new claims requirement.

    outputIdentity.Claims.Add( new Claim( System.IdentityModel.Claims.ClaimTypes.Name, principal.Identity.Name ) );
    outputIdentity.Claims.Add( new Claim( ClaimTypes.Role, "Manager" ) );

 

If now I change “Manager” for “Employee”, RIA will raise an “Access Denied” exception:

image

 

This works for Roles, but what if you wanted to express access rules based on other claim types (repeat after me: Roles are Claims, Claims are not Roles)? Let’s say, you have to be in a “Manager” role working in the “HR” group to approve sabbaticals: 

 

[RequiresRole("Manager")]
[RequiresGroupMembership("HR")]
public void ApproveSabbatical(Employee current)

All you need to do is create a RequiresGroupMembershipAttribute and inspect the claim set for presence of a claim of type “Group” with value “HR”:

 

public class RequiresGroupMembershipAttribute : AuthorizationAttribute
{
    public string requiredGroup;
 
    public RequiresGroupMembershipAttribute(string group)
    {
        this.requiredGroup = group;
    }

    public override bool Authorize(IPrincipal principal)
    {
        var identity = ((principal as IClaimsPrincipal).Identity as IClaimsIdentity);
        return identity.Claims.Exists(c => c.ClaimType == "http://hrapp/Org/Groups" && 
                                                 c.Value == requiredGroup);
        
    }
}

 

and add the claim to the STS:

outputIdentity.Claims.Add( new Claim( System.IdentityModel.Claims.ClaimTypes.Name, principal.Identity.Name ) );
outputIdentity.Claims.Add( new Claim( ClaimTypes.Role, "Manager" ) );
outputIdentity.Claims.Add( new Claim( "http://hrapp/Org/Groups", "HR") );

 

Of course you could refactor the above to accept any claims, not just Groups or Roles. Something like:

 

public class RequiresClaimAttribute : AuthorizationAttribute
{
    private string requiredClaimType;
    private string requiredClaimValue; 

    public RequiresClaimAttribute(string claimType, string value)
    {
        this.requiredClaimType = claimType;
        this.requiredClaimValue = value;
    }

    public override bool Authorize(IPrincipal principal)
    {
        var identity = ((principal as ClaimsPrincipal).Identity as IClaimsIdentity);
        return identity.Claims.Exists(c => c.ClaimType == requiredClaimType && 
                                           c.Value == requiredClaimValue);
    }
}

 

and

public class RequiresGroupMembershipAttribute : RequiresClaimAttribute
{
    public RequiresGroupMembershipAttribute(string group) : base("http://hrapp/Org/Groups", group)
    {
    }
}

 

Then, you could write:

[RequiresRole("Manager")]
[RequiresGroupMembership("HR")]
[RequiresClaim("http://hrapp/Location", "UK")]
public void ApproveSabbatical(Employee current)
  
 

Caveats: this is sample code, so I’ve not invested much in error handling, etc. This is “as is”.

Comments (2)

  1. Rachida Dukes says:

    Eugenio, using RIA Services for silverlight 4 in Visual Studio 2010, can you show us how to build UI in silverlight project, and populate two XAML files (for example) with the users and the roles data?

    Thanks so much for good work.

    Rachida Dukes

  2. jlchereau says:

    After successfully executing your tutorial, I wanted to go one step further by adding integration with Windows Azure.

    Basically what I do is:

    1) Create a blank solution;

    2) Add a windows Azure Cloud Service (without any role);

    3) Add a silverlight business application;

    4) Add the web site projet of the Silverlight business application to the Roles of the Windows Azure Cloud Service

    5) Add a new ASP.NET security token service hosted in its default location, i.e. http://localhost/STSWebSite

    6) Add STS reference to the web site projet of the Silverlight application and designate the new ASP.NET security token service

    7) Hit F5 and run

    I get : System.Windows.RiaDomaineOperationException: Load operation failed for query ‘GetUser’. The remote server retruned an error: NotFound.

    The application runs without authentication if I comment out

    WebContext.Current.Authentication.LoadUser(this.Application_UserLoaded, null);

    in App.xaml.cs

    Please not that I have tried on at least too machines where your turorial completes successfully.

    Any idea what might occur?