Simple implementation of ASP.NET Web API Basic authentication security

Securing Web API with simple basic authentication and consuming it from the client code

Authentication in web services is a bit more different than with web pages because of one simple reason. There is no UI for entering credentials to authenticate to consume service. Credentials need to be supplied during the service call. This can be easily done through headers.

However, although it is really simple to implement basic authentication, it has one major disadvantage which is credentials are sent in plain text in every request (SSL is mandatory to encrypt requests)

Apart from this disadvantage it is pretty easy to use it especially if your client is an application because basic authentication is following standard (RFC 2617) and it is supported by many platforms and browsers.

The following implementation uses Web.config to store credentials for the sake of simplicity. Depending on your requirements you can use any authentication provider. Credentials keys in web config are following the pattern, so it is pretty easy to introduce new users

  <appSettings>
    <add key="auth.realm" value="1562EEDB4C124DDC83E70FF3C86CA9B1"/>
    <add key="auth.user.User1" value="pass123"/>
    <add key="auth.user.MyUser" value="Pass321"/>
  </appSettings>
    
Note

Realm value is also stored in Web.config since we will use single realm without any segmentation. More about realm value you can find at http://tools.ietf.org/html/rfc2617#section-1.2

Since we are following pattern for credentials we can easily read credentials from web.config with a simple LINQ expression

const String AUTH_USER_PREFIX= "auth.user.";
        static readonly IDictionary<String, String> logins = ConfigurationManager.AppSettings.AllKeys.Where(k => k.StartsWith(AUTH_USER_PREFIX))
                                                            .ToDictionary(key => key.Replace(AUTH_USER_PREFIX, String.Empty), value => ConfigurationManager.AppSettings.Get(value));
    

ConfigurationManager class is static, so we do not need to go and read values on every request. Instead we'll store credentials in a singletone instance of IDictionary<String,String> variable and use it for aech authenticated request.

Handling the authenticated and non authenticated requests

Now we need to handle request authentication in a custom IHttpModule implementation. Since we have our credentials in web.config file we only need to read headers and compare them with ones in a web.config

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Net.Http.Headers;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Web;

namespace WebApi.BasicAuthentication.Modules
{
    public class BasicAuthHttpModule : IHttpModule
    {
        const String AUTH_USER_PREFIX= "auth.user.";
        static readonly IDictionary<String, String> logins = ConfigurationManager.AppSettings.AllKeys.Where(k => k.StartsWith(AUTH_USER_PREFIX))
                                                            .ToDictionary(key => key.Replace(AUTH_USER_PREFIX, String.Empty), value => ConfigurationManager.AppSettings.Get(value));
        public void Init(HttpApplication context)
        {
            context.AuthenticateRequest += OnAuthenticateRequest;
            context.EndRequest += OnEndRequest;
        }

        private static void OnAuthenticateRequest(object sender, EventArgs e)
        {
            var authHeaders = HttpContext.Current.Request.Headers["Authorization"];
            if (authHeaders != null)
            {
                var authHeadersValue = AuthenticationHeaderValue.Parse(authHeaders);
                if (authHeadersValue.Scheme.Equals("basic", StringComparison.OrdinalIgnoreCase) && !String.IsNullOrWhiteSpace(authHeadersValue.Parameter))
                {

                    try
                    {
                        var credentials = authHeadersValue.Parameter;
                        var encoding = Encoding.GetEncoding("iso-8859-1");
                        credentials = encoding.GetString(Convert.FromBase64String(credentials));
                        string name = credentials.Split(':').First();
                        string password = credentials.Split(':').Last();

                        if (logins.Any(l => l.Key.Equals(name) && l.Value.Equals(password)))
                        {
                            //Set the principal for validated user

                            var principal = new GenericPrincipal(new GenericIdentity(name), null);
                            Thread.CurrentPrincipal = principal;
                            if (HttpContext.Current != null)
                            {
                                HttpContext.Current.User = principal;
                            }
                        }
                        else
                        {
                            //Authentication failed
                            HttpContext.Current.Response.StatusCode = 401;
                        }
                    }
                    catch (FormatException)
                    {
                        HttpContext.Current.Response.StatusCode = 401;
                    }

                }
            }
        }

        private static void OnEndRequest(object sender, EventArgs e)
        {
            var response = HttpContext.Current.Response;
            if (response.StatusCode == 401)
            {
                //Addh eaders if authentication failed
                response.Headers.Add("WWW-Authenticate",
                    string.Format("Basic realm=\"{0}\"", ConfigurationManager.AppSettings.Get("auth.realm")));
            }
        }

        public void Dispose() { }

    }
}
    

We have authentication module which will handle our request authentication but until we declare it in web.config it will not be picked up by ASP.NET runtime, so we register it in modules section in system.webServer section

  <system.webServer>
    <modules>
      <add name="BasicAuthHttpModule"
        type="WebApi.BasicAuthentication.Modules.BasicAuthHttpModule, WebApi.BasicAuthentication"/>
    </modules>


  </system.webServer>
    

We still do not have authentication engaged and that is because we need to decorate our controller actions with [Authorize] attribute to trigger our custom authentication module.

using System.Net;
using System.Net.Http;
using System.Web.Http;

namespace WebApi.BasicAuthentication.Controllers
{
    public class DefaultController : ApiController
    {
        [Authorize]
        public HttpResponseMessage Get()
        {
            return Request.CreateResponse(HttpStatusCode.OK, "Hello from Default controller!");
        }
    }
}

    

Now if we try to access /api/default we will be prompted to authenticate ourself to get the response. You can try any of the username and password combination we have declared in our web.config file. Voila, we have our Web API protected with basic authentication.

Consuming Web API protected with Basic authentication

No to get the response from endpoint which is protected with basic security we need to inject our credentials into headers using same ISO-8859-1 and base64.

using System;
using System.IO;
using System.Net;
using System.Text;

namespace WebApi.BasicAuthentication.Client
{
    class Program
    {
        static void Main(string[] args)
        {
            String username = "User1";
            String password = "pass123";
            String url = "http://localhost:59496/api/default";

            String encoded = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes($"{username}:{password}"));
            var request = WebRequest.Create(url);

            request.Headers.Add("Authorization", $"Basic {encoded}");

            var response = request.GetResponse();
            using (var streamReader = new StreamReader(response.GetResponseStream())) {
               Console.WriteLine(streamReader.ReadToEnd());
            }

            Console.ReadLine();

        }
    }
}

    

Both Web API and client code are available from the download section of this article so you can download it and try it yourself.

Disclaimer

Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.


About the author

DEJAN STOJANOVIC

Dejan is a passionate Software Architect/Developer. He is highly experienced in .NET programming platform including ASP.NET MVC and WebApi. He likes working on new technologies and exciting challenging projects

CONNECT WITH DEJAN  Loginlinkedin Logintwitter Logingoogleplus Logingoogleplus

JavaScript

read more

SQL/T-SQL

read more

Umbraco CMS

read more

PowerShell

read more

Comments for this article