Thinktecture.IdentityModel: WIF Support for WCF REST Services and OData

By: Dominick Baier, DevelopMentor Author & Instructor

 

The latest drop of Thinktecture.IdentityModel includes plumbing and support for WIF, claims and tokens for WCF REST services and Data Services (aka OData).

Cibrax
has an alternative implementation that uses the WCF Rest Starter Kit. His recent post reminded me that I should finally “document” that part of our library.
Features include:
  • generic plumbing for all WebServiceHost derived WCF services
  • support for SAML and SWT tokens
  • support for ClaimsAuthenticationManager and ClaimsAuthorizationManager
  • based solely on native WCF extensibility points (and WIF)
This post walks you through the setup of an OData / WCF DataServices endpoint with token authentication and claims support. This sample is also included in the codeplex download along a similar sample for plain WCF REST services.

Setting up the Data Service
To prove the point I have created a simple WCF Data Service that renders the claims of the current client as an OData set.
 

public
class ClaimsData
{
    public IQueryable<ViewClaim> Claims
    {
        get { return GetClaims().AsQueryable(); }
    }
     private List<ViewClaim> GetClaims()
    {
        var claims = new List<ViewClaim>();
        var identity = Thread.CurrentPrincipal.Identity as IClaimsIdentity;
         int id = 0;
        identity.Claims.ToList().ForEach(claim =>
            {
                claims.Add(new ViewClaim
                {
                   Id = ++id,
                   ClaimType = claim.ClaimType,
                   Value = claim.Value,
                   Issuer = claim.Issuer
                });
            });
         return claims;
    }
}
 
…and hooked that up with a read only data service:
 
public class ClaimsDataService : DataService<ClaimsData>
{
    public static void InitializeService(IDataServiceConfiguration config)
    {
        config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
    }
}

Enabling WIF
Before you enable WIF, you should generate your client proxies. Afterwards the service will only accept requests with an access token – and svcutil does not support that.

All the WIF magic is done in a special service authorization manager called the FederatedWebServiceAuthorizationManager. This code checks incoming calls to see if the Authorization HTTP header (or X-Authorization for environments where you are not allowed to set the authorization header) contains a token. This header must either start with SAML access_token= or WRAP access_token= (for SAML or SWT tokens respectively).

For SAML validation, the plumbing uses the normal WIF configuration. For SWT you can either pass in a SimpleWebTokenRequirement or the SwtIssuer, SwtAudience and SwtSigningKey app settings are checked.If the token can be successfully validated, ClaimsAuthenticationManager and ClaimsAuthorizationManager are invoked and the IClaimsPrincipal gets established.

The service authorization manager gets wired up by the FederatedWebServiceHostFactory:

public
class FederatedWebServiceHostFactory : WebServiceHostFactory
{
    protected override ServiceHost CreateServiceHost(
      Type serviceType, Uri[] baseAddresses)
    {
        var host = base.CreateServiceHost(serviceType, baseAddresses);
         host.Authorization.ServiceAuthorizationManager =
          new FederatedWebServiceAuthorizationManager();
        host.Authorization.PrincipalPermissionMode = PrincipalPermissionMode.Custom;
         return host;
    }
}

The last step is to set up the .svc file to use the service host factory (see the sample download).

Calling the Service
To call the service you need to somehow get a token. This is up to you. You can either use WSTrustChannelFactory (for the full CLR), WSTrustClient (Silverlight) or some other way to obtain a token. The sample also includes code to generate SWT tokens for testing – but the whole WRAP/SWT support will be subject of a separate post.

I created some extensions methods for the most common web clients (WebClient, HttpWebRequest, DataServiceContext) that allow easy setting of the token, e.g.:

public
static void SetAccessToken(this DataServiceContext context,
  string token, string type, string headerName)
{
    context.SendingRequest += (s, e) =>
    {
        e.RequestHeaders[headerName] = GetHeader(token, type);
    };
}

Making a query against the Data Service could look like this:
 
static void CallService(string token, string type)
{
    var data = new ClaimsData(new Uri("https://server/odata.svc/"));
    data.SetAccessToken(token, type);
     data.Claims.ToList().ForEach(c =>
        Console.WriteLine("{0}\n {1}\n ({2})\n", c.ClaimType, c.Value, c.Issuer));
}
HTH