The Freak Parade

Strange noises from the mind of Nathan Stults…
  • rss
  • Home
  • About The Freak Parade

Flowing Identity from a Client to a Service when using RESTful WCF Part 2 - A Solution

September 1, 2008

This post will describe a technique to transparently insert a custom HTTP header into all requests generated by a WCF REST proxy, as well as a technique to extract that HTTP header from the request at the service for the purpose of flowing the digital identity of an authenticated user from a REST client to a REST service. In this case, digital identity is represented by an authorization token, which is just a string. You can read a detailed description of the problem in Part 1 of this post. A complete sample project containing the full source code of the solution is on CodePlex.

The basic idea is to insert a custom action into the WCF message pipeline that will extract a security token representing the identity of the currently logged in user and add the value of that token (just a string) to the outgoing API call (an HTTP request) by way of a custom HTTP header. Once the request is received by the service, a custom action inserted into the receive pipeline checks for the custom header and, upon finding it, de-serializes the identity token and uses the claims contained within it to set an appropriate security context (in this case by setting Thread.CurrentPrincipal).

Setting a Custom HTTP Header - The Easy Way

On the client side, I am making the assumption that a user has been authenticated somehow, is associated with a set of claims, and that a representation of the user along with her claims can be found at Thread.CurrentPrincipal. In anticipation of using Microsoft’s new Zermatt Identity framework, which introduces IClaimsPrincipal and IClaimsIdentity, we implemented our own versions of these interfaces. They will eventually be replaced by the Zermatt types once we start integrating Zermatt into our application. IClaimsPrincipal maintains a reference to IClaimsIdentity, which has a property of type IList<IClaim> that represents all the authorization relevant attributes of a user. Please see this post for a discussion of claims based identity management. The interface IClaim is pretty much just a name value pair.

Adding a custom header to a request is trivial if you add the header in the same scope as the service call made on the proxy. You can just wrap the entire call in an OperationContextScope using statement, and use the Headers property of WebOperationContext.Current:


using (var factory = new WebChannelFactory<imyservice>())
{
  IMyService proxy = factory.CreateChannel();
  using (new OperationContextScope((IClientChannel) proxy))
  {
    WebOperationContext.Current.OutgoingRequest.Headers.Add('x-mycompany-auth',
                                                            'tokenString');
    proxy.DoItToIt();
  }
}

That technique works, and would probably be just fine if you only needed to add the authorization token to a few select calls. However, in our scenario, we want to add the token to *every* call, so wrapping each API call with all that scoping garbage isn’t going to cut it. It’s ugly and about as un-DRY as it gets.

Extending WCF - The Transparent Way

The solution, of course, is to take advantage of the WCF extensibility points available to both the client and the server. We want to choose spots in the messaging pipelines as close to the actual delivery/receipt of the message as possible.That would be the the Message Inspection phase on the client and the Operation Context Initialization phase on the server.

WCF can be made to jump through our hoops by following these steps:

On the client side we need to

1. Define the MessageInspector

2. Create a behavior to register the MessageInspector

3. Install the behavior on the channel factory.

On the service side we need to

4. Define a OperationContextInitializer

5. Create a behavior to register the extension

6. Apply the behavior to the service.

The end result is that Thread.CurrentPrincipal on the service side will always reference an IClaimsPrincipal with the same claims associated with the Thread.CurrentPrincipal on the service consumer, and we can test those claims to authorize access, as demonstrated in

7. Use.

1. The MessageInspector


 public class ClientAuthMessageInspector : IClientMessageInspector
 {
        #region IClientMessageInspector Members

        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {
            //Get the HttpRequestMessage property from the Message
            var httpRequest =
                request.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty;

            //Make sure we have a valid property, or create one
            if (httpRequest == null)
            {
                httpRequest = new HttpRequestMessageProperty();
                request.Properties.Add(HttpRequestMessageProperty.Name, httpRequest);
            }

            //Add your token to the header. Simple as that.
            //In real life, we injected an ITokenProvider using an IoC,
            //instead of using static methods directly, but that was
            //overkill for a sample.
            httpRequest.Headers.Add(AuthenticationHelper.AUTH_TOKEN_HEADER_NAME,
                                    AuthenticationHelper.GetAuthTokenForCurrentUser());

            return null;
        }

        public void AfterReceiveReply(ref Message reply, object correlationState)
        {
            //nop
        }

        #endregion
 }

2. The Behavior


public class ClientAuthBehavior : IEndpointBehavior
{
        private readonly ClientAuthMessageInspector _messageInspector;

        public ClientAuthBehavior(ClientAuthMessageInspector messageInspector)
        {
            _messageInspector = messageInspector;
        }

        #region IEndpointBehavior Members

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            //Install the message inspector
            clientRuntime.MessageInspectors.Add(_messageInspector);
        }

        //no-op method implementations elided for clarity.
	//See the sample project for the complete
	//code listing.

        #endregion
}

3. Apply


 private void InitializeChannelFactory()
 {
    _channelFactory = new WebChannelFactory<imonkeyshavingservice>("shavingService");
    _channelFactory.Endpoint.Behaviors.Add(new ClientAuthBehavior());
 }

4. On the Service Side…OperationContextInitializer


 public class ClaimsAuthContextInitializer : ICallContextInitializer
 {
     #region ICallContextInitializer Members
      public Object BeforeInvoke(InstanceContext instanceContext,
                                IClientChannel channel,
                                Message message)
     {
         DetectCurrentUser();
         return null;
     }

     public void AfterInvoke(Object correlationState)
     {
     }
     #endregion

     private static void DetectCurrentUser()
     {
         if (WebOperationContext.Current == null)
             throw new InvalidOperationException("Only HTTP web requests are supported for this version of the API");

          string authToken = WebOperationContext.Current
             .IncomingRequest
             .Headers[AuthenticationHelper.AUTH_TOKEN_HEADER_NAME];

         if (AuthenticationHelper.SetCurrentUserFromAuthToken(authToken) == false)
         {
             WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.Forbidden;
                throw new UnauthorizedAccessException(
                 "No authentication token was found in the headers of the current request.");
         }
    }
 }

5. The Behavior


[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    public class ClaimsAuthServiceBehavior : Attribute, IServiceBehavior
    {
        #region IServiceBehavior Members

        public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
        }

        public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase,
                                         Collection<serviceendpoint> endpoints,
                                         BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
            {
                var cd = cdb as ChannelDispatcher;

                if (cd != null)
                {
                    foreach (EndpointDispatcher ed in cd.Endpoints)
                    {

                        foreach (var dispatchOperation in
                            ed.DispatchRuntime.Operations)
                        {
                            dispatchOperation.CallContextInitializers
                                .Add(new ClaimsAuthContextInitializer());
                        }

                    }
                }
            }
        }

        #endregion

6. Apply


 static void Main(string[] args)
 {
      _host = new WebServiceHost(typeof(MonkeyShavingService),
                                 new Uri("http://localhost:8000"));

      _host.Description.Behaviors.Add(new ClaimsAuthServiceBehavior());

       var binding = new WebHttpBinding();
       _host.AddServiceEndpoint(typeof(IMonkeyShavingService), binding, "");

       _host.Open();

       Console.WriteLine("Press any key to exit.");
       Console.ReadKey();
 }

7. Last but not least, Use (this is a sample service method that restricts access to certain users)


 public Monkey PutMonkeyInShaver(Monkey monkey)
 {
      //Only authenticated users can add monkeys to the shaver
      if (CurrentUser == null)
      {
       SetStatusCode(HttpStatusCode.Forbidden,
                     "Only authenticated users can put monkeys in the shaver.");
       return null;
      }

      _monkeys.Add(monkey);
      ShaveMonkey(monkey);
      return monkey;
 }

 private IClaimsPrincipal CurrentUser
 {
     get { return Thread.CurrentPrincipal as IClaimsPrincipal; }
 }

It works, but…

Our ultimate solution does not extend or take advantage of any of the claims based security features built into WCF. While it may be possible to do so, it wasn’t immediately obvious to us how to go about it, so the solution presented here handles authorization out of band from the built in WCF authorization mechanisms.

As you may have guessed, I am NOT a WCF expert. That means that the approach outlined here could very well be a cheap hack for a problem solvable in a much more elegant way. If by some chance you happen across this post and know that to be the case, I’d love to hear your thoughts on the subject :) I wasn’t able to find any guidance during my own research that hinted at a more appropriate approach to solving this problem, but that certainly doesn’t mean one doesn’t exist.

About the Sample Application

The sample application can be downloaded here and includes a complete, end to end REST API implemented in WCF. It includes separate DLL’s to represent the Service, the Contracts, the Client, a Service Host and a Utility DLL that contains shared authentication and authorization code. To get the most from the sample you should run the Service Consumer project, a Windows Forms application that allows you to invoke various operations of the API while impersonating users with various roles. In addition to demonstrating a possible approach to flowing a claims-based security token through a REST API, the sample demonstrates a fully functional, RESTful WCF service, which may be useful to you in its own right.

Here is a screen shot of the sample Windows Forms service client:

What’s Next

In the future I will be enhancing the sample to demonstrate our approach to defining authorization policies that operate against a set of claims using our Simple Expression Evaluator project, currently on CodePlex. This technique will show how a simple but powerful and flexible claims-aware rules engine can easily be constructed using any expression evaluator and a set of claims.

Share: These icons link to social bookmarking sites where readers can share and discover new web pages.
  • del.icio.us
  • DZone
  • Digg
  • Google Bookmarks
  • Ma.gnolia
  • Technorati
hello
Categories
Identity, REST
Comments rss
Comments rss
Trackback
Trackback

« Flowing Identity from a Client to a Service when using RESTful WCF Part 1 - The Problem A Response to "On Passion" »

discussion by DISQUS

Add New Comment

  • Subscribe:  This Thread
  • Go to:  My Comments ·  Community Page
  • Sort thread by:

    Viewing 6 Comments

    Thanks. Your comment is awaiting approval by a moderator.

    Do you already have an account? Log in and claim this comment.

      • ^
      • v
      • Permalink
      • Admin
        • Remove Post
        • Block username
        • Block email
        • Block IP address
      karl_mcguinness 4 months ago 1 point

      Please login to rate.

      Do you already have an account? Log in and claim this comment.

      Good to see other folks doing the same thing. We just finished prototyping a similar approach. Instead of using custom headers, have you thought about using WS-Security Token Profiles over HTTP headers so that you can leverage the existing profiles and library support? (see http://www.xml.com/pub/a/2003/12/17/dive.html) . WS-Security has a standard profile for SAML tokens so you can also use this approach to normalize your SOAP and REST strategies in WCF.
      reply  edit  reblog  flag
      1 /people/karl_mcguinness/ /people/karl_mcguinness/following/
      • ^
      • v
      • Permalink
      • Admin
        • Remove Post
        • Block username
        • Block email
        • Block IP address
      nstults 4 months ago 1 point

      Please login to rate.

      Do you already have an account? Log in and claim this comment.

      That looks to be a much better approach, thank you for posting the suggestion and link. How is your prototype going? Does it work?
      reply  edit  reblog  flag
      1 /people/nstults/ /people/nstults/following/ http://www.thefreakparade.com
      • ^
      • v
      • Permalink
      • Admin
        • Remove Post
        • Block email
        • Block IP address
      JoeG 2 months ago 1 point

      Please login to rate.

      Do you already have an account? Log in and claim this comment.

      I'm a bit confused (nothing new for me ;). I thought that in the "Identity Triad", the subject (in this case, your Windows Forms client) wasn't supposed to have access to the claims contained in the token. The IP creates the claims, then hands them to the subject in the form of an encrypted secure token. They are then passed to the RP in the encrypted form. I don't believe that Zermatt will facilitate handling of the claims by the subject via IClaimsIdentity, only by the RP.
      reply  edit  reblog  flag
      /people/e8f0f46dec6e786e48c9b088e48f968b/
      • ^
      • v
      • Parent
      • Permalink
      • Admin
        • Remove Post
        • Block username
        • Block email
        • Block IP address
      nstults 2 months ago 1 point

      Please login to rate.

      Do you already have an account? Log in and claim this comment.

      In my example there isn't actually an Identity Provider involved - the service consumer (the Windows Forms client) is acting as the IP, which is where your confusion comes from. In a real scenario the service consumer would likely facilitate the retrieval of an encrypted token for the logged in user from a real IP and then use the technique I described to flow that token to the service. In my simplified example, however, the service consumer is constructing a dummy token because this sample isn't concerned with that part of the Identity relationship. And I don't think you would call any piece of software the "Subject" - the Subject, in my limited understanding, is the conceptual entity being authenticated, i.e. a user. Usually the RP and the application the user is logging into are the same, so the application itself is the Relying Party (RP). In my sample, logically, the windows forms application is just the user interface to a distributed application, and the whole thing could be considered the Relying Party. The role of the Identity Provider is somewhat obscured by the fact that the UI (service consumer) is pretending to be the IP.
      reply  edit  reblog  flag
      1 /people/nstults/ /people/nstults/following/ http://www.thefreakparade.com
      • ^
      • v
      • Parent
      • Permalink
      • Admin
        • Remove Post
        • Block email
        • Block IP address
      JoeG 2 months ago 1 point

      Please login to rate.

      Do you already have an account? Log in and claim this comment.

      Thanks for the clarification of the "Subject" concept; a lot of this stuff is still gelling in my brain. I guess where I'm still a little fuzzy on what you're trying to achieve comes from the phrase, "On the client side...a representation of the user along with her claims can be found at Thread.CurrentPrincipal." I'm not so sure this is the case with the Identity Metasystem model I have been reading about as of late. As I understand Zermatt, IClaimsPrincipal and IClaimsIdentity are concepts that don't apply to the client-side of things (thinking of the client as a web browser in the ASP.NET world, and a Windows Form in the rich client world).
      reply  edit  reblog  flag
      /people/e8f0f46dec6e786e48c9b088e48f968b/
      • ^
      • v
      • Parent
      • Permalink
      • Admin
        • Remove Post
        • Block username
        • Block email
        • Block IP address
      nstults 2 months ago 1 point

      Please login to rate.

      Do you already have an account? Log in and claim this comment.

      I think you may be reading into the Identity Metasystem too specifically - in my understanding, client or server is not the question, simply the relative, identity related roles of whoever is participating in an identity transaction. I see no reason a smart client (such as a Win Forms app) couldn't play the role of a Relying Party. You can't really compare a web browser front end to a smart client front end as if they were counterparts - a smart client may, for instance, require the users claims to appropriately render its UI (i.e. hide or show certain features based on access rules), whereas a web app will rely on the "server" to fill this role as the server generates the user interface. I can't imagine client side code in a browser being able to examine claims, or wanting to, but it is not at all unreasonable for a smart client to do so. In other scenarios a web app may actually be acting as a "client", delegating to a distributed service layer for its business logic and concerning itself only with rendering a UI, and in that scenario the web app may also want access to a users claims. If the front and and back end are just tiers of a single application, I don't think this is a problem. I could be misunderstanding some very important point here, though, that has been known to happen.
      reply  edit  reblog  flag
      1 /people/nstults/ /people/nstults/following/ http://www.thefreakparade.com
     
    discussion by DISQUS

    Add New Comment

    Trackbacks

    (Trackback URL)

    close ()

    status via twitter

    recent comments (follow comments)

      View Profile ยป
      Powered by Disqus · Learn more
      close Reblog this comment
      Powered by Disqus · Learn more
      blog comments powered by Disqus

      Subscribe

      Calendar

      September 2008
      M T W T F S S
      « Aug   Oct »
      1234567
      891011121314
      15161718192021
      22232425262728
      2930  

      Recent Posts

      • You Can’t Fill an Imaginary Hole
      • I don’t know but I’ve been told, ETL is gettin’ mighty old. BAM! BAM! EDA! I want my data right away!
      • Be Prepared To Be Surprised
      • Google Chrome, I could kiss you! (Or, multi-process browsers are a really good idea)
      • New Open Source .NET CMS/EPS Platform Released Today: Sense/Net 6.0 Beta 1

      Recent Comments

      • Ashwani on Rule Based Access Control using an Expression Evaluator
      • Richers Blog on Identity’s new Identity - Part 3, The Technology
      • sandra on ESB’s for the Microsoft (.NET) Platform
      • nstults on Content Management Systems (CMS) for the .NET Platform
      • Adz on Content Management Systems (CMS) for the .NET Platform

      Tags

      TDD Testing

      Meta

      • Log in
      • Entries RSS
      • Comments RSS
      • WordPress.org
      rss Comments rss valid xhtml 1.1 design by jide powered by Wordpress get firefox