Web API & HTTP Basic Authentication

 
 
  • Gérald Barré

ASP.NET Web API is a framework for easily creating web services (http). Securing these services is a common need. There are many ways to do it. In this article we will implement basic HTTP authentication (RFC 2617).

#HTTP Basic Authentication

To authenticate someone, there are three methods:

  • What you know, for example a couple username / password
  • What you have, for example a smart card
  • What you are, for example the recognition of the iris

The basic authentication method uses a username/password and is therefore the first type of authentication. Its operation is very simple:

When the user tries to access the resource without being authenticated, the server returns status 401 (Unauthorized) and the following header:

WWW-Authenticate: Basic realm = "http://www.sample.com"

The client understands that he must authenticate with the Basic method and re-execute the same request but including the header:

Authorization: Basic TWV6aWFudG91OjEyMzQ1Ng==

TWV6aWFudG91OjEyMzQ1Ng== is the username followed by : and then its password, all encoded in base 64. The original value is Meziantou:123456. The server can thus authenticate the client. This header must be sent to each request.

#Pipeline Web API

ASP.NET Web API consists of a pipeline for processing queries. This is notably composed of a series of DelegatingHandler. These handlers process incoming requests one after the other, but also the responses. A handler can either pass the request to the next handler, or return the result directly to the previous handler and stop the progress in the pipeline.

A handler can for example perform the following treatments:

  • Read and modify the headers of the request
  • Add a header to the answer
  • Authenticate the user
  • Validate a request before it reaches the controller
  • Trace / Log queries and responses

You will find a more complete diagram on the official site: http://www.asp.net/posters/web-api/ASP.NET-Web-API-Poster.pdf

DelegatingHandler can integrate into the pipeline at 2 levels:

  • Globally to the API: they intercept all messages
  • By route: they only intercept the messages intended for the route with which they are associated

#Implementation

We will create a DelegatingHandler that:

  • At the entrance is trying to authenticate the user if the header is present
  • At the output adds the header if the status of the response is 401 (Unauthorized)
C#
public abstract class BasicAuthMessageHandler : DelegatingHandler
{
    private const string BasicAuthResponseHeader = "WWW-Authenticate";
    private const string BasicAuthResponseHeaderValue = "Basic Realm=\"{0}\"";

    protected BasicAuthMessageHandler()
    {
    }

    protected BasicAuthMessageHandler(HttpConfiguration httpConfiguration)
    {
        InnerHandler = new HttpControllerDispatcher(httpConfiguration);
    }

    protected virtual string GetRealm(HttpRequestMessage message)
    {
        return message.RequestUri.Host;
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // Process request
        AuthenticationHeaderValue authValue = request.Headers.Authorization;
        if (authValue != null && !String.IsNullOrWhiteSpace(authValue.Parameter) &&
            string.Equals(authValue.Scheme, "basic", StringComparison.OrdinalIgnoreCase))
        {
            // Try to authenticate user
            IPrincipal principal = ValidateHeader(authValue.Parameter);
            if (principal != null)
            {
                request.GetRequestContext().Principal = principal;
            }
        }

        return base.SendAsync(request, cancellationToken) // Send message to the InnerHandler
            .ContinueWith(task =>
            {
                // Process response
                var response = task.Result;
                if (response.StatusCode == HttpStatusCode.Unauthorized &&
                    !response.Headers.Contains(BasicAuthResponseHeader))
                {
                    response.Headers.Add(BasicAuthResponseHeader,
                    string.Format(BasicAuthResponseHeaderValue, GetRealm(request)));
                }

                return response;
            }, cancellationToken);
    }

    private IPrincipal ValidateHeader(string authHeader)
    {
        // Decode the authentication header & split it
        var fromBase64String = Convert.FromBase64String(authHeader);
        var lp = Encoding.Default.GetString(fromBase64String);
        if (string.IsNullOrWhiteSpace(lp))
            return null;

        string login;
        string password;
        int pos = lp.IndexOf(':');
        if (pos < 0)
        {
            login = lp;
            password = string.Empty;
        }
        else
        {
            login = lp.Substring(0, pos).Trim();
            password = lp.Substring(pos + 1).Trim();
        }

        return ValidateUser(login, password);
    }

    protected abstract IPrincipal ValidateUser(string userName, string password);
}

The ValidateUser method is abstract and must be implemented according to your needs:

C#
public class SampleBasicAuthMessageHandler : BasicAuthMessageHandler
{
    protected override IPrincipal ValidateUser(string userName, string password)
    {
    if (string.Equals(userName, "Meziantou", StringComparison.OrdinalIgnoreCase) && password == "123456")
        return new GenericPrincipal(new GenericIdentity(userName, "Basic"), new string[0]);

    return null;
    }
}

All that remains is to declare the handler:

C#
public void Configuration(IAppBuilder app)
{
    HttpConfiguration config = new HttpConfiguration();
    config.Routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );

    config.MessageHandlers.Add(new SampleBasicAuthMessageHandler());
    app.UseWebApi(config);
}

The complete example is available on GitHub: Web API - Basic Authentication.

warn: With this type of authentication it is strongly recommended to use HTTPS to protect the identifiers that otherwise circulate in clear on the network.

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub