WCF web api + developer APi keys

4 minute read

I found myself with the requirement of needing custom authorization for a rest-style web API. The requirements would be something along the lines of Netflix, Twitter, etc. APIs whereby there is a need to identify an application making calls to the service. Most of these APIs seem to follow a familiar pattern of dishing out a developer API key consisting of a private key and a public key. If you’re not familiar with the basics of public key cryptography see http://en.wikipedia.org/wiki/Public-key_cryptography.

This post is not about that, though! The purpose for this is to show the code I ended with to integrate the solution into WCF Web API (http://wcf.codeplex.com/). An additional requirement was that some of the API required the auth and some of it didn’t.

I also investigated the OAuth 1.0 spec and it appeared to support my scenario but with extra, unnecessary steps. (I haven’t yet investigated OAuth 2.0 fully).

There appears to be a lot of information floating around on this subject (see http://weblogs.asp.net/cibrax/archive/2011/04/15/http-message-channels-in-wcf-web-apis-preview-4.aspx and http://haacked.com/archive/2011/10/19/implementing-an-authorization-attribute-for-wcf-web-api.aspx, amongst others).

Anyway, I’ll run through the steps of what I did for this so far….

So first, I created a blank ASP.NET MVC3 application. I added some routes in the RegisterRoutes method in Global.asax.cs, one for each API:

<div id="codeSnippetWrapper" class="csharpcode-wrapper">   <div id="codeSnippet" class="csharpcode">     <pre class="alt"><span id="lnum1" class="lnum">   1:</span> routes.Add(<span class="kwrd">new</span> ServiceRoute(<span class="str">&quot;api/authedapi&quot;</span>,</pre> <!--CRLF-->

<pre class="alteven"><span id="lnum2" class="lnum">   2:</span>     <span class="kwrd">new</span> HttpServiceHostFactory</pre> <!--CRLF-->

<pre class="alt"><span id="lnum3" class="lnum">   3:</span>         {</pre> <!--CRLF-->

<pre class="alteven"><span id="lnum4" class="lnum">   4:</span>             Configuration =</pre> <!--CRLF-->

<pre class="alt"><span id="lnum5" class="lnum">   5:</span>                 <span class="kwrd">new</span> AuthHttpConfiguration(Container)</pre> <!--CRLF-->

<pre class="alteven"><span id="lnum6" class="lnum">   6:</span>                     {</pre> <!--CRLF-->

<pre class="alt"><span id="lnum7" class="lnum">   7:</span>                         EnableTestClient = <span class="kwrd">true</span>,</pre> <!--CRLF-->

<pre class="alteven"><span id="lnum8" class="lnum">   8:</span>                         CreateInstance = ResolveServiceInstances</pre> <!--CRLF-->

<pre class="alt"><span id="lnum9" class="lnum">   9:</span>                     }</pre> <!--CRLF-->

<pre class="alteven"><span id="lnum10" class="lnum">  10:</span>         },</pre> <!--CRLF-->

<pre class="alt"><span id="lnum11" class="lnum">  11:</span>     <span class="kwrd">typeof</span> (AuthedApi)));</pre> <!--CRLF-->

<pre class="alteven"><span id="lnum12" class="lnum">  12:</span> routes.Add(<span class="kwrd">new</span> ServiceRoute(<span class="str">&quot;api/keys&quot;</span>,</pre> <!--CRLF-->

<pre class="alt"><span id="lnum13" class="lnum">  13:</span>     <span class="kwrd">new</span> HttpServiceHostFactory</pre> <!--CRLF-->

<pre class="alteven"><span id="lnum14" class="lnum">  14:</span>         {</pre> <!--CRLF-->

<pre class="alt"><span id="lnum15" class="lnum">  15:</span>             Configuration =</pre> <!--CRLF-->

<pre class="alteven"><span id="lnum16" class="lnum">  16:</span>                 <span class="kwrd">new</span> HttpConfiguration</pre> <!--CRLF-->

<pre class="alt"><span id="lnum17" class="lnum">  17:</span>                     {</pre> <!--CRLF-->

<pre class="alteven"><span id="lnum18" class="lnum">  18:</span>                         EnableTestClient = <span class="kwrd">true</span>,</pre> <!--CRLF-->

<pre class="alt"><span id="lnum19" class="lnum">  19:</span>                         CreateInstance = ResolveServiceInstances</pre> <!--CRLF-->

<pre class="alteven"><span id="lnum20" class="lnum">  20:</span>                     }</pre> <!--CRLF-->

<pre class="alt"><span id="lnum21" class="lnum">  21:</span>         },</pre> <!--CRLF-->

<pre class="alteven"><span id="lnum22" class="lnum">  22:</span>     <span class="kwrd">typeof</span> (ApiKeys)));</pre> <!--CRLF--></div>

</div>

Things to note about this code are:

  • I have set the CreateInstance delegate to a function which uses my IOC container to resolve the types.
  • The configuration for the un-authed api is just the standard way to set up a WCF web API route – the authedapi, however uses a custom Configuration type derived from HttpConfiguration. It does this in order to set up a custom ServiceAuthorizationManager object on the Service Authorization Behavior:
   1: public class AuthHttpConfiguration : HttpConfiguration
   2: {
   3:     private readonly IUnityContainer _container;
   4:  
   5:     public AuthHttpConfiguration(IUnityContainer container)
   6:     {
   7:         _container = container;
   8:     }
   9:  
  10:     protected override void OnConfigureServiceHost(HttpServiceHost serviceHost)
  11:     {
  12:         ServiceDebugBehavior serviceDebugBehavior = serviceHost.Description.Behaviors.OfType<ServiceDebugBehavior>().FirstOrDefault();
  13:         if (serviceDebugBehavior != null)
  14:             serviceDebugBehavior.IncludeExceptionDetailInFaults = true;
  15:  
  16:         foreach (var behavior in serviceHost.Description.Behaviors.OfType<ServiceAuthorizationBehavior>())
  17:         {
  18:             behavior.ServiceAuthorizationManager = _container.Resolve<MyServiceAuthorizationManager>();
  19:         }
  20:         base.OnConfigureServiceHost(serviceHost);
  21:     }
  22: }

Also note that I use an IOC container to resolve the types. (WCF web API lends itself nicely to the use of dependency injection and unit testing).

Plugging in the custom authorization manager (http://msdn.microsoft.com/en-us/library/ms731774.aspx) allows each requests auth to be handled in a central location. Authorization decisions are made in the CheckAccessCore method, which returns true when access is granted and false when access is denied.

So, here’s the shell of MyServiceAuthorizationManager…

   1: public class MyServiceAuthorizationManager : ServiceAuthorizationManager
   2:  {
   3:      private readonly IApiKeyRepository _apiKeyRepository;
   4:  
   5:      public MyServiceAuthorizationManager(IApiKeyRepository repository)
   6:      {
   7:          _apiKeyRepository = repository;
   8:      }
   9:  
  10:      protected override bool CheckAccessCore(OperationContext operationContext)
  11:      {
  12:         // logic to determine auth status for request...
  13:      } 
  14: }

The logic in CheckAccessCore is something like the following in my scenario:

  • Retrieve the auth-related data from the http request (probably from the http auth header or contained in the query string). This will contain the public key and also the request signature.
  • This contains the public key so look up the private key from the public key in a key repository (The public key is sent in the request. The private key is stored separately by both parties).
  • Generate a signature using the public and private keys and compare it to the one sent in the request.
  • If they match return true, otherwise return false.

This seems to work for me for now but I don’t yet know how this compares with the other methods or indeed whether OAuth 2.0 will better facilitate the requirements for this scenario.

Categories:

Updated:

Comments