In the first part of this series I described how you can secure your WCF service. The first part describes some of the most common security modes, how you can configure them and how you can add custom authentication.
Part 1 ends using the TransportWithMessageCredential
security mode with a custom username/password validator. However, authorization in that scenario is not covered. That’s going to be the focus of this post.
Authorization in WCF
WCF allows us to restrict the access of operations using an attribute named PrincipalPermission
, much like what you can do with ASP.NET MVC’s Authorize
attribute. For example, this will restrict an operation to users with the Admin
role:
[PrincipalPermission(SecurityAction.Demand, Role="Admin")]
public void Remove(int userId)
{
//...
}
Or if you want to specify alternative roles, for example Admin
or Manager
:
[PrincipalPermission(SecurityAction.Demand, Role="Admin")]
[PrincipalPermission(SecurityAction.Demand, Role="Manager")]
public void Remove(int userId)
{
//...
}
If you want to enable more complicated scenarios, for example if you need to check if the user has two roles you need to test that programmatically. I’ll show you that in a second, but first I need to mention the ServiceSecurityContext
class.
If you read the first post, and ran the gitub project with the examples you probably noticed that the test service is getting the identity of the user from two distinct sources:
ServiceSecurityContext.Current.PrimaryIdentity
Thread.CurrentPrincipal.Identity.Name
You probably also noticed that when the TransportWithMessageCredential
security mode was used we were able to get the current user’s username from the ServiceSecurityContext
but not from Thread.CurrentPrincipal
. This is because we need to setup a custom authorization policy (because we are using custom authentication).
The ServiceSecurityContext
contains the “identity” of the user that is logged in. I mentioned “identity” in quotes because these “identities” are instances of IIdentity
. However, the fact that there’s an identity in ServiceSecurityContext
is not enough to enable you to use the PrincipalPermission
attribute, for example this won’t work even if your username is johndoe:
[PrincipalPermission(SecurityAction.Demand, Name="johndoe")]
public void Remove(int userId)
{
//...
}
You need to create an custom authorization policy and in it, set the Principal (this will become clear with an example).
Creating a custom IAuthorizationPolicy
The IAuthorizationPolicy
interface is defined in the System.IdentityModel
assembly (it’s the same that contains UserNamePasswordValidator
) and is:
public interface IAuthorizationPolicy : IAuthorizationComponent
{
ClaimSet Issuer { get; }
bool Evaluate(EvaluationContext evaluationContext, ref object state);
}
And IAuthorizationComponent
just defines an string getter for a property named Id
.
The Id
needs to be unique, but can be whatever you want; I couldn’t find information about ClaimSet
, all the examples I’ve found just implement it as return ClaimSet.System;
, so if you have more information about this, please let me know in the comments, otherwise… just return ClaimSet.System
.
The evaluate method, if it returns true
it stops other authorization polices from being evaluated (there can be multiple authorization policies, that will become clear when we create the configuration to add our own configuration policy).
The three important questions that need to be answered regarding what you need to do in your implementation of this interface to enable authorization are:
- Where is the custom authenticated user’s username?
- What do I have to do in the
IAuthorizationPolicy
‘sEvaluate
method to enable the use of thePrincipalPermission
attribute in the service implementation? - What do I have to do in the
IAuthorizationPolicy
‘sEvaluate
method to enable the use of Thread.CurrentPrincipal
1. Where is custom authenticated user’s username?
In the EvaluationContext
instance that is passed in as a parameter to the Evaluate
method there’s a property named Properties
that is of type IDictionary<string, object>
.
In that dictionary you’ll find a key named “Identities” which contains a List<IIdentity>
instance. Usually it will only contain one element. If you are using a custom UserNamePasswordValidator (see Part 1) and the the class name is MyCustomUserNamePasswordValidator
you’ll find one item in that list whose authentication type is “MyCustomUserNamePasswordValidator”.
Here’s how you can get to the instance of that IIdentity
that represents the authenticated user, using MyCustomUserNamePasswordValidator
:
var identity = (evaluationContext.Properties["Identities"] as List<IIdentity>).Single(i => i.AuthenticationType == "MyCustomUserNamePasswordValidator");
The username is here: identity.Name
.
2. What do I have to do in the IAuthorizationPolicy’s Evaluate method to enable the use of the PrincipalPermission attribute in the service implementation?
You have to set a property named “Principal” in evaluationContext.Properties
to an instance of an IPrincipal. The particular type of IPrincipal
implementation you choose is not important, because all of them will implement IsInRole
, and that’s what is important to enable the use of the PrincipalPermission
attribute.
Here’s an example of creating a ClaimsPrincipal
with the identity fetched in (1) and adding a role named Admin
:
var claimsIdentity = new ClaimsIdentity(identity);
claimsIdentity.AddClaim(new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Role, "Admin"));
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
evaluationContext.Properties["Principal"] = claimsPrincipal;
3. What do I have to do in the IAuthorizationPolicy’s Evaluate method to enable the use of Thread.CurrentPrincipal
Just set it, for example:
Thread.CurrentPrincipal = claimsPrincipal;
And it will be available in your service’s operations. The reason for (3) is that if you want to enable scenarios where you want to check if your user has both the role “Admin” and “Manager” you can do this:
[PrincipalPermission(SecurityAction.Demand, Authenticated = true )]
public void Remove(int userId)
{
var principal = Thread.CurrentPrincipal;
if (!(principal.IsInRole("Admin") && principal.IsInRole("Manager")))
throw new System.ServiceModel.Security.SecurityAccessDeniedException("Insuficient privileges");
//...
}
A custom authorization policy complete example
public class CustomAuthorizationPolicy : IAuthorizationPolicy
{
public bool Evaluate(EvaluationContext evaluationContext, ref object state)
{
var identity = (evaluationContext.Properties["Identities"] as List<IIdentity>).Single(i => i.AuthenticationType == "CustomUserNamePasswordValidator");
var claimsIdentity = new ClaimsIdentity(identity);
claimsIdentity.AddClaim(new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Role, "Admin"));
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
evaluationContext.Properties["Principal"] = claimsPrincipal;
Thread.CurrentPrincipal = claimsPrincipal;
return true;
}
public System.IdentityModel.Claims.ClaimSet Issuer
{
get { return ClaimSet.System; }
}
public string Id
{
get { return Guid.NewGuid().ToString(); }
}
}
Configuration for the new Authorization Policy
To add the new authorization policy we need to edit our WCF configuration and add it as part of a serviceBehavior
, namely through serviceAuthorization
, whose principalPermissionMode
we must set to Custom
, here’s an example for the configuration:
<behaviors>
<serviceBehaviors>
<behavior>
...
<serviceAuthorization principalPermissionMode="Custom">
<authorizationPolicies>
<add policyType="TheNamespace.CustomAuthorizationPolicy, TheAssemblyName"/>
</authorizationPolicies>
</serviceAuthorization>
...
</behavior>
</serviceBehaviors>
</behaviors>
You can find a working example here: https://github.com/ruidfigueiredo/wcfsecuritysurvivalguide-part2-authorization
If you would want to see a more elaborate custom UserNamePasswordValidator
and IAuthorizationPolicy
, maybe using ASP.NET Identity, please let me know in the comments so I can decide if it’s worth dedicating some time to write about that.