API Key Authorization Through Query String In ASP.NET Web API AuthorizationFilterAttribute

We will see how API key authorization (verification) through query string would be implemented In ASP.NET Web API AuthorizationFilterAttribute
5 March 2012
7 minutes read

Related Posts

Update on the 29th of June, 2012:

The nuget package I use inside this post is not compatable with ASP.NET Web API RC and will not be with ASP.NET Web API RTM. I have another package named WebAPIDoodle which has the same funtionality as here. The source code for WebAPIDoodle: https://github.com/tugberkugurlu/WebAPIDoodle

Update on the 17th of October, 2012:

Confession

@SoyUnEmilio has pointed out a very good topic in his comment and I replied back to him but I would like to make it clear inside the post itself, too.

I wrote this article at the early stages of the framework and I now think that I mixed the concepts of authantication and authorization a lot. So, this blog post does not point you to  a good way of implementing authantication and authorization inside you ASP.NET Web API application. I don't want to delete the stuff that I don't like anymore. So, this blog post will stay as it is but I don't want to give the wrong impression as well. As the framework is now more mature, my thoughts on authentication and authorization is shaped better.

In my opinion, you should implement your authantication through a message handler and you almost always don't want to try to perform authorization inside that handler. You may only return "Unauthorized" response inside the message handler (depending on your situation) if the user is not authanticated at all but that should be futher than that. Here is a message handler sample for the API Key authentication: ApiKeyAuthenticationHandler.cs.

As for the authorization part, it can be handled by the System.Web.Http.AuthorizeAttribute. The AuthorizeAttribute checks against the Thread.CurrentPrincipal. So, the principal that you have supplided inside your message handler will be checked against. You also have a change to perform role or user name based authorization through the AuthorizeAttribute.

If you are going to build a REST API, you don’t probably want to expose all the bits and pieces to everyone. Even so, you would like to see who are making request for various reasons. One of the best ways is the API Key verification to enable that.

In a nutshell, I'll try to explain how it works. You give each user an API key (a GUID would be suitable) and ask them to concatenate this key on the request Uri as query string value. This business logic of assigning the keys works like this. But IMO, one thing is important. No matter what you do, do not manage the API key as admin. You need to find a way to make users manage their keys. User should be able to reset their API key whenever they want and by doing that, old key must gets invalid. As much as this part of the process is important, how you very them on your application is another issue.

With ASP.NET Web API, it is pretty easy to intercept a request and change the behavior of that request in any level. For API Key verification, we have two options: 1) Creating a DelegetingHandler and register it as a message handler. 2) Creating an Authorization filter which will be derived from AuthorizationFilterAttribute class. With one of those two ways, we can verify the user according to API Key supplied.

Honestly, I am not sure which one would be the best option. But, it is certain that if you don’t want the whole application to be API key verified, a filter is the best option and it can be applied to an action, controller and globally for entire application. On the other hand, message handlers are involved before the filters. So, that might seem better if you would like your whole app to be API key verified.

I have created an API key verification filter and I tried to make it generic so that it can be applied for all different verification scenarios. Let me show you what I mean.

First of all, go get the bits and pieces through Nuget. The package is TugberkUg.Web.Http and it is a prerelease package for now:

PM> Install-Package TugberkUg.Web.Http -Pre

This package contains other stuff related to ASP.NET Web API. You can check out the source code on https://github.com/tugberkugurlu/ASPNETWebAPISamples/tree/master/TugberkUg.Web.Http/src/TugberkUg.Web.Http.

For the purpose of this post, what we are interested in is ApiKeyAuthAttribute class and IApiKeyAuthorizer interface. The logic works in a very simple way:

We need a class which will be derived from IApiKeyAuthorizer interface.

public interface IApiKeyAuthorizer {

    bool IsAuthorized(string apiKey);
    bool IsAuthorized(string apiKey, string[] roles);
}

As you can see, there are two methods here. First one takes only one parameter which is the API key. This method will be invoked if you try to verify the request only based on API key. Unlike the first one, the second method takes two parameters: API key as string and roles as array of string. This one will be invoked if you try to verify the request based on API key and roles.

I have created an in memory API key authorizer to try this out:

public class InMemoryApiKeyAuthorizer : IApiKeyAuthorizer {

    private static IList<User> _validApiUsers = new List<User> { 

        new User { ApiKey = "d9c99318-53b6-4846-8613-e5aecb473066", 
            Roles = new List<Role>() { 
                new Role { Name = "Admin" } 
            }
        },
        new User { ApiKey = "dd97a5aa-704e-4c9e-9bd5-5e2828392eee", 
            Roles = new List<Role>() { 
                new Role { Name = "Customer" } 
            }
        },
        new User { ApiKey = "b2e684d7-8807-4232-b5fc-1a6e80c175c0", 
            Roles = new List<Role>() { 
                new Role { Name = "Admin" } 
            }
        },
        new User { ApiKey = "36171dc0-4925-4b12-a162-0d6d193acb75" },
        new User { ApiKey = "c8028fae-4887-4e91-8fa5-9655adae6ec1" },
        new User { ApiKey = "c4bdb227-095a-4fde-8db5-1c96d86e897a" },
        new User { ApiKey = "ff10e537-44d5-49b3-add2-6011f54de996" },
        new User { ApiKey = "3dcd18cf-e373-4436-9171-aa7f20dae23c" },
        new User { ApiKey = "17b2663d-df81-4f63-b10e-5ed918a920cf" },
        new User { ApiKey = "44fffbf2-8b32-4c4c-834a-518dd0279efa" }
    };

    public bool IsAuthorized(string apiKey) {

        return
            _validApiUsers.Any(x => x.ApiKey == apiKey);
    }

    public bool IsAuthorized(string apiKey, string[] roles) {

        if(_validApiUsers.Any(x => 
            x.ApiKey == apiKey && x.Roles.Where(r => 
                roles.Contains(r.Name)).Count() > 0)) {

            return true;
        }

        return false;
    }
}

Here, normally you would look at the supplied information about the request and return either true or false. As you can imagine, the request will be verified if true is returned. If not, then we will handle the unauthorized request in a specific way. We will get to there in a minute.

I am not sure these two parameters enough to see if the request is legitimate or not. I had a chance to easily supply the System.Web.Http.Controllers.HttpActionContext as parameter to these methods but I didn’t. Let me know what you think.

Now we have our logic implemented, we can now apply the filter to our application. As you know, filters are attributes and our attribute is as follows:

public class ApiKeyAuthAttribute : AuthorizationFilterAttribute {

    public ApiKeyAuthAttribute(string apiKeyQueryParameter, Type apiKeyAuthorizerType);

    public string Roles { get; set; }

    protected virtual void HandleUnauthorizedRequest(HttpActionContext actionContext);
    public override void OnAuthorization(HttpActionContext actionContext);
    
}

ApiKeyAuthAttribute has only one constructor and takes two parameters: apiKeyQueryParameter as string for the query string parameter name which will carry the API key and apiKeyAuthorizerType as Type which is the type of Api Key Authorizer which implements IApiKeyAuthorizer interface. We also have a public string property called Roles which accepts comma separated list of roles which user needs to be in.

The usage of the filter is simple as follows:

[ApiKeyAuth("apiKey", typeof(InMemoryApiKeyAuthorizer), Roles = "Admin")]
public class CarsController : ApiController {

    public string[] GetCars() {

        return new string[] { 
            "BMW",
            "FIAT",
            "Mercedes"
        };
    }
}

Now, when we hit the site without API Key and with the legitimate user API key which is not under the Admin role, we will get 401.0 Unauthorized response back:

apiKey

This is the default behavior when an unauthorized user sends a request but can be overridden. You need to simply override the HandleUnauthorizedRequest method and implement your own logic.

When we send a request with a proper API key, we will get the expected result:

image

The sample I used here is also on GitHub: https://github.com/tugberkugurlu/ASPNETWebAPISamples/tree/master/TugberkUg.Web.Http/src/samples/ApiKeyAuthAttributeSample

If you have any advice, please comment or fork me on GitHub.