Basic Authentication with ASP.NET Web API Using Authentication Filter

Authorization filters and action filters have been around for a while in ASP.NET Web API but there is this new authentication filter introduced in Web API 2. Authentication filters have their own place in the ASP.NET Web API pipeline like other filters. Historically, authorization filters have been used to implement authentication and there is ton of samples out there with all kinds of authentication implemented in authorization filters. Web API 2 introduces the authentication filter so that authentication concerns can be separated out of authorization filter and put into an authentication filter.

This blog post is just a quick introduction to writing a custom authentication filter for implementing HTTP Basic Authentication. There is a full-blown example here, if you are interested in writing a production-strength filter.

First up, we create the filter class BasicAuthenticator implementing the IAuthenticationFilter interface. We also derive from Attribute so that we can apply the filter on action methods, like so.

public class EmployeesController : ApiController
{
    [BasicAuthenticator(realm: "Magical")]
    public HttpResponseMessage Get(int id)
    {
        return Request.CreateResponse<Employee>(
        new Employee()
        {
            Id = id,
            FirstName = "Johnny",
            LastName = "Law"
        });
    }
}

There are two interesting methods that we need to implement in the filter – (1) AuthenticateAsync and (2) ChallengeAsync.

AuthenticateAsync contains the core authentication logic. If authentication is successful, context.Prinicipal is set. Otherwise, context.ErrorResult is set to UnauthorizedResult, which basically gets translated to a “401 – Unauthorized” HTTP response status code.

public class BasicAuthenticator : Attribute, IAuthenticationFilter
{
    private readonly string realm;
    public bool AllowMultiple { get { return false; } }

    public BasicAuthenticator(string realm)
    {
        this.realm = "realm=" + realm;
    }

    public Task AuthenticateAsync(HttpAuthenticationContext context,
                                  CancellationToken cancellationToken)
    {
        var req = context.Request;
        if (req.Headers.Authorization != null && 
                req.Headers.Authorization.Scheme.Equals(
                          "basic", StringComparison.OrdinalIgnoreCase))
        {
            Encoding encoding = Encoding.GetEncoding("iso-8859-1");
            string credentials = encoding.GetString(
                                  Convert.FromBase64String(
                                      req.Headers.Authorization
                                                   .Parameter));
            string[] parts = credentials.Split(':');
            string userId = parts[0].Trim();
            string password = parts[1].Trim();

            if (userId.Equals(password)) // Just a dumb check
            {
                var claims = new List<Claim>()
                {
                    new Claim(ClaimTypes.Name, "badri")
                };
                var id = new ClaimsIdentity(claims, "Basic");
                var principal = new ClaimsPrincipal(new[] { id });
                context.Principal = principal;
            }
        }
        else
        {
            context.ErrorResult = new UnauthorizedResult(
                     new AuthenticationHeaderValue[0],
                                          context.Request);
        }

        return Task.FromResult(0);
    }

    public Task ChallengeAsync(
                     HttpAuthenticationChallengeContext context,
                            CancellationToken cancellationToken) {}
}

For basic authentication, when there is a 401, we are supposed to send WWW-Authenticate header and the right place to write such challenge related logic will be the ChallengeAsync method. This is where things get interesting because it is not so straight forward to add headers here. The recommended approach is to create a class implementing IHttpActionResult and set an instance of it to context.Result, like so.

public Task ChallengeAsync(HttpAuthenticationChallengeContext context,
                                      CancellationToken cancellationToken)
{
    context.Result = new ResultWithChallenge(context.Result, realm);
    return Task.FromResult(0);
}

Here is the result class. The crux of this class is in the ExecuteAsync method and that is where we set the WWW-Authenticate response header indicating the scheme and the realm.

public class ResultWithChallenge : IHttpActionResult
{
    private readonly IHttpActionResult next;
    private readonly string realm;

    public ResultWithChallenge(IHttpActionResult next, string realm)
    {
        this.next = next;
        this.realm = realm;
    }

    public async Task<HttpResponseMessage> ExecuteAsync(
                                CancellationToken cancellationToken)
    {
        var res = await next.ExecuteAsync(cancellationToken);
        if (res.StatusCode == HttpStatusCode.Unauthorized)
        {
            res.Headers.WwwAuthenticate.Add(
               new AuthenticationHeaderValue("Basic", this.realm));
        }

        return res;
    }
}

For setting the WWW-Authenticate response header, we created a class. However, it is possible to get away without creating one, like this.

public Task ChallengeAsync(HttpAuthenticationChallengeContext context,
                               CancellationToken cancellationToken)
{
    var result = await context.Result.ExecuteAsync(cancellationToken);
    if (result.StatusCode == HttpStatusCode.Unauthorized)
    {
        result.Headers.WwwAuthenticate.Add(
                new AuthenticationHeaderValue(
                    "Basic", "realm=" + this.realm));
    }
    context.Result = new ResponseMessageResult(result);
}

However, this approach will not work with MVC since there is no ResponseMessageResult. For the sake of consistency, it is better to create our own class. Also, the code above changes the pipeline behavior slightly. For these reasons, it is recommended to create a class implementing IAuthenticationFilter (the initial approach in this post).

Take a look at this SO answer from David Matson related to this. There is an open work item here and if you are interested in getting some help from the framework itself, vote on the work item.

Advertisements

5 thoughts on “Basic Authentication with ASP.NET Web API Using Authentication Filter

  1. Thanks for the post,
    I have two questions. What is Realm for?
    And secondly how do I allow certain action methods to no be authenticated.
    [AllowAnonymous] does not work. the filter is still called.

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