Patrick Desjardins Blog
Patrick Desjardins picture from a conference

How to add active directory (AD) security to WCF services with attribute?

Posted on: 2013-06-27

If you are using WCF web services with active directory (AD) you may want to have a more atomic authorization process than setting the security by IIS. You may want to allow for specific method some group when some other may only have access to others.

The first step is to create a custom attribute. This example show you how to add a single role but you could modify this code to allow multiple roles.

 public class SecureOperationAttribute : Attribute, IOperationBehavior { public string Role { get; set; } public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters){} public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation) { } public void Validate(OperationDescription operationDescription) {} public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation) {

dispatchOperation.Invoker = new SecureOperationInvoker(Role, dispatchOperation); } } 

Once the attribute is set you need to create the invoker.

public class SecureOperationInvoker : IOperationInvoker { 
  private readonly IOperationInvoker_baseInvoker; 
  private readonly string_operationName; 
  private readonly string_role;

  public SecureOperationInvoker(string role, DispatchOperation operation) {
    _role = role;
    _baseInvoker = operation.Invoker;
    _operationName = operation.Name; 
  }

  public object Invoke(object instance, object[] inputs, out object[] outputs) { 
    if (!UserIsInRole(_role)) { 
      throw new FaultException(string.Format("Authentification fail. The operation '{0}' requires the role '{1}'.",_operationName,_role), new FaultCode("Authentification")); 
    } 
    return_baseInvoker.Invoke(instance, inputs, out outputs); 
  }

  public object[] AllocateInputs() { 
    return_baseInvoker.AllocateInputs(); 
  }

  public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state) { 
    return_baseInvoker.InvokeBegin(instance, inputs, callback, state); 
  }

  public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result) {
    return_baseInvoker.InvokeEnd(instance, out outputs, result); 
  }

  public bool IsSynchronous { 
    get { return_baseInvoker.IsSynchronous; } 
  } 
    
  public bool UserIsInRole(string role) { 
    var winPrinc = new WindowsPrincipal(OperationContext.Current.ServiceSecurityContext.WindowsIdentity); 
    return winPrinc.IsInRole(role); 
  } 
}

Of course, this example a small representation of what should have been done in a real system. In fact, the UserIsInRole shouldn't be hardcoded to use WindowsPrincipal. It should be injected to be able to test without having to use WindowsPrincipal. Also, the role variable may in fact not be exactly the name of the active directory (AD) group, so you may need to inject also a mechanism to translate the role used by the attribute to the AD group.

Here is how to use the attribute over your WCF contract.

[ServiceContract] 
public interface IMyEntityXYZ { 
  [OperationContract] 
  [SecureOperation(Role = "Admin")] 
  void Action(DTOXYZ[] xyz); 
} 

What I like is that you can set the security on the contract. It's simple for the maintenance cause the operation signature contains who has access the the method defined. It's also easy to test if you are using injection to create a stub method for the validation of the role.