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 == "https://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( "https://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("https://hrapp/Org/Groups", group)
    {
    }
}

 

Then, you could write:

 [RequiresRole("Manager")]
[RequiresGroupMembership("HR")]
[RequiresClaim("https://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”.