WCF web api + developer APi keys
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">"api/authedapi"</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">"api/keys"</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.
Comments