ASP.NET Web Api Basic Authentication

I have recently moved a project over from WCF Web Api Preview 6 to ASP.NET Web Api (Beta). Part of that migration involved moving my message handler which dealt with authentication. In the old API I could only authenticate requests to the whole API or nothing at all. This is obviously not ideal, as we may want some parts of the API to be public, but other to only be available to authenticated users.

I have eventually come up with this for my message handler …

public class BasicAuthenticationMessageHandler : DelegatingHandler
{
    private readonly ILogger _logger;

    private class Credentials
    {
        public string Username { get; set; }
        public string Password { get; set; }
    }

    public BasicAuthenticationMessageHandler(ILogger logger)
    {
        _logger = logger;
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
    {
        if (request.Headers.Authorization != null)
        {
            var credentials = ExtractCredentials(request.Headers.Authorization);
            if (credentials != null && ValidateUser(credentials))
            {
                var identity = new GenericIdentity(credentials.Username, "Basic");
                request.Properties.Add(HttpPropertyKeys.UserPrincipalKey, new GenericPrincipal(identity, new string[0]));
            }
        }
        return base.SendAsync(request, cancellationToken);
    }

    private bool ValidateUser(Credentials credentials)
    {
        if (!Membership.ValidateUser(credentials.Username, credentials.Password))
        {
            _logger.Debug("BasicAuthenticationMessageHandler.ExtractCredentials: Authentication failed for user '{0}'", credentials.Username);
            return false;
        }
        return true;
    }

    private Credentials ExtractCredentials(AuthenticationHeaderValue authHeader)
    {
        try
        {
            if (authHeader == null)
            {
                _logger.Debug("BasicAuthenticationMessageHandler.ExtractCredentials: auth header is null, returning null");
                return null;
            }

            if (authHeader.Scheme != "Basic")
            {
                _logger.Debug("BasicAuthenticationMessageHandler.ExtractCredentials: unsupported scheme {{0}), returning null", authHeader.Scheme);
                return null;
            }

            var encodedUserPass = authHeader.Parameter.Trim();
            var encoding = Encoding.GetEncoding("iso-8859-1");
            var userPass = encoding.GetString(Convert.FromBase64String(encodedUserPass));
            var parts = userPass.Split(":".ToCharArray());
            return new Credentials {Username = parts[0], Password = parts[1]};
        }
        catch (Exception ex)
        {
            _logger.Error(ex, "BasicAuthenticationMessageHandler.ExtractCredentials: Cannot extract credentials.");
            return null;
        }
    }
 }
 

To hook the handler into your API simply add the handler to the global configuration …

var logger = container.Resolve<ILogger>();
GlobalConfiguration.Configuration.MessageHandlers.Add(new BasicAuthenticationMessageHandler(logger));

The key thing to remember is to not try and take over the handling of returning the 401 (Unauthorised) response code to the client, let the framework do that for you via the [Authorize] attribute. Like so …

[Authorize]
public class MyController : ApiController
{
...
}

or, for individual actions …

public class MyController : ApiController
{
    [Authorize]
    public string Get(string id)
    {
    ...
    }
}
About these ads

16 responses to “ASP.NET Web Api Basic Authentication

  1. Thanks for a great post. Can’t really get it to work though. Even if request.Headers.Authorization is null (line 18), my action method with the [Authorize] attribute still gets invoked.

    • What should happen in that case is the handler will just let the request continue. Then the framework should see the [Authorize] attribute on the method and send back a 401 response because there is no Principal within the request properties. I used a chrome extension to make sure all of this worked. I also wrote a test client application which can be driven by xml test data (ie – expected response codes for given requests). If you’re still having problems I can post that code too.

    • My authentication doesn’t follow a flow as such, that is really up to the client to figure out.

      If a request requires authentication and the credentials are not supplied via the “Auth” header then a 401 is returned. If the client sends an anonymous request which returns a 401 it is down to the client to try again with credentials.I’ve only used a chrome extension to test the authentication side of my API and that sets the Authorization header. Which is also what my clients will be doing.I have also written some test client code using the new HttpClient class (it came along at the same time as the Web Api) and again, I just set the Authorization header and got back the expected results.

    • it looks like you got your answer via stack overflow. An earlier version of my message handler did have code in place to send back the WWW-Authenticate header in the response, but for some reason I couldn’t quite get it to work properly. I found that the framework was adding other authenticate header in there as well so my client ended up picking the wrong (ie – non Basic) authentication method. The solution I posted in this blog article assumes the caller knows what needs authenticating and what method to use. Which I’ve handled by publishing documentation along with my API. Not ideal, but it does the job :)

  2. Alright, now I got it to run. But it gets invoked even if there is no [Authorize] attribute on the called action. How would I got about to make this messagehandler only run for actions with the [Authorize] attribute? Or would I need to implement my own
    AuthorizationFilterAttribute to be able to get that kind of control?

    • that’s a very good question!

      tbh, I am not sure how to get that functionality. I would be very interested to find out though. It sounds like a stackoverflow question in the making ;)

  3. Antony, thanks for the code – I can’t believe there isn’t a built-in solution for this with webapi.

    One thing others might find useful though is to enable Roles with the Authorize attribute just change 

    request.Properties.Add(HttpPropertyKeys.UserPrincipalKey, new GenericPrincipal(identity, new string[0])); 

    to

    request.Properties.Add(HttpPropertyKeys.UserPrincipalKey, new GenericPrincipal(identity, Roles.GetRolesForUser(credentials.Username)));

    I also had to remove the existing UserPrincipalKey property before I could add the new one (not sure why that was).

    Thanks again,
    Clint

  4. with MVC4 RC Microsoft changed how the authentication token is handled.

    By disassembling the System.Web.Http.AuthorizeAttribute, I found that I had to change line 24 of the posted code to something similar to this:
    Thread.CurrentPrincipal = new GenericPrincipal(identity, new string[0]);

  5. This article has been a great help – thank you! Can anyone explain why, as Renato observed, the Authentication only works if Thread.CurrentPrincipal is set? This strikes me as a code-smell, and I’m sure there must be a better way to complete the Authentication.

  6. Hi Antony,

    Is there a reason we leave the returning of 401 message to the filters rather than doing it in the handler itself?

    Thanks
    Nrupal

    • It’s been quite some time since I worked on this. But if I remember correctly, dealing with 401′s in the handler caused me problems. I vaguely remember getting issues with the real error code being hidden by a 500 code.

      Give it a try and let me know how you get on. I’ll happily update the blog post with your findings.

  7. I added the handler in the WebApiConfig.cs to the route. I thought I implemented this correctly, but I get a response of: “ExceptionMessage”:”Unable to connect to SQL Server database.” when I pass the Authorization data via a REST service. I can go to the api url in the address bar and I get redirected to Login and am able to login and then see the api data. I am using MVC 5 with Entity Framework (and OWIN) if that helps. Any ideas?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s