Implementing Basic HTTP Authentication from pure ASP.NET Code


Configuring a web site to be protected by basic authentication may not be a possible option on some hosting platforms. Because a there are no operational switches to flip, a developer could implement his own workaround - getting back down to the basics.

Basic HTTP Authentication is a set of HTTP headers passed between the server and client. Reverse engineering this, we would be able to implement it within our own code.

HTTP Headers Exchanged

The usual conversation between the server and client would be something like the following.

  1. Client browser requests resource

     GET path/to/resource/which/is/protected HTTP/1.1
    
  2. Server responds with an not authorized status, but also informs the client browser that basic HTTP authentication is available.

     Status-Code: 401; Not Authorized
     WWW-Authenticate: Basic realm ="www.yoursite.com"
    
  3. At this point, the client browser would ask the user for the user name and password, and then send the following request through.

     GET path/to/resource/which/is/protected HTTP/1.1
     Authorization: BasicBase64UserNamePasssword
    
  4. If the user name and password is correct, the web server then responds with the content. If not, it will jump to step 2 and resume from there.

ASP.NET Global.asax Implementation

With the above logic defined, I have supplied some sample code which you are free to use within your own code base.

To hook it up to your code, just modify your global.asax file to something like the following.

public class MvcApplication : HttpApplication
{
        private static readonly DefaultHttpApplicationDriver AuthenticationDriver =
                new DefaultHttpApplicationDriver();
        private static readonly DictionaryUserAuthenticationStore AuthenticationStore =
                new DictionaryUserAuthenticationStore();

        protected void Application_Start()
        {
                // usual ASP.NET registration stuff

                AuthenticationStore.RegisterUser("username", "password");
        }

        protected void Application_AuthenticateRequest(object sender, EventArgs e)
        {
                BasicAuthenticationExtension.UponAuthentication(
                        AuthenticationDriver, AuthenticationStore);
        }
}

The code listing below describes the main implementation logic as we discovered.

public interface IUserAuthenticationStore
{
        bool AuthenticateUser(string userName, string password);
}

public interface IHttpApplicationDriver
{
        void RespondAccessDenied();
        string RequestAuthenticationDetails();
        void SetUserPrinciple(string userName, string password);
        bool IsUserAuthenticated();
}

public static class BasicAuthenticationExtension
{
        public static void UponAuthentication(IHttpApplicationDriver driver,
                IUserAuthenticationStore authenticationStore)
        {
                if (driver == null)
                {
                        throw new ArgumentNullException("driver");
                }
                if (authenticationStore == null)
                {
                        throw new ArgumentNullException("authenticationStore");
                }

                if (driver.IsUserAuthenticated())
                {
                        return;
                }

                const string basicAuthenticationPrefix = "Basic";

                string transmittedAuthentication;
                string decodedBase64Data;
                int separatorPosition;
                string userNamePart = null;
                string passwordPart = null;

                var shouldGrantAccess =
                        HasTransmittedAuthentication(driver, basicAuthenticationPrefix, out transmittedAuthentication)
                        && HasSentUserNameAndPassword(transmittedAuthentication, basicAuthenticationPrefix, out decodedBase64Data, out separatorPosition)
                        && IsUserValid(authenticationStore, decodedBase64Data, separatorPosition, out userNamePart, out passwordPart);

                if (shouldGrantAccess)
                {
                        driver.SetUserPrinciple(userNamePart, passwordPart);
                        return;
                }

                driver.RespondAccessDenied();
        }

        private static bool IsUserValid(IUserAuthenticationStore authenticationStore, string decodedBase64Data,
                int separatorPosition, out string userNamePart, out string passwordPart)
        {
                userNamePart = decodedBase64Data.Substring(0, separatorPosition);
                passwordPart = decodedBase64Data.Substring(separatorPosition + 1);

                bool isUserValid = authenticationStore.AuthenticateUser(userNamePart, passwordPart);
                return isUserValid;
        }

        private static bool HasSentUserNameAndPassword(string transmittedAuthentication, string basicAuthenticationPrefix,
                out string decodedBase64Data, out int separatorPosition)
        {
                string base64Part = transmittedAuthentication.Substring(basicAuthenticationPrefix.Length);
                var base64Bytes = Convert.FromBase64String(base64Part);
                decodedBase64Data = Encoding.UTF8.GetString(base64Bytes);

                separatorPosition = decodedBase64Data.IndexOf(":", StringComparison.Ordinal);

                bool hasSentValidSeparator = separatorPosition >= 0;
                return hasSentValidSeparator;
        }

        private static bool HasTransmittedAuthentication(IHttpApplicationDriver driver, string basicAuthenticationPrefix,
                out string transmittedAuthentication)
        {
                transmittedAuthentication = driver.RequestAuthenticationDetails();

                bool hasSentAuthenticationHttpHeader =
                        transmittedAuthentication != null
                        && transmittedAuthentication.StartsWith(basicAuthenticationPrefix, true, CultureInfo.InvariantCulture);

                return hasSentAuthenticationHttpHeader;
        }
}

Below’s code listings are just simple wrappers which implements the IHttpApplicationDriver and IUserAuthenticationStore respectively.

public class DefaultHttpApplicationDriver : IHttpApplicationDriver
{
        public void RespondAccessDenied()
        {
                HttpContext httpContext = HttpContext.Current;
                HttpRequest httpRequest = httpContext.Request;
                HttpResponse httpResponse = httpContext.Response;

                string headerValue = string.Format("Basic realm=\"{0}\"", httpRequest.Url.Host);

                httpResponse.ClearContent();
                httpResponse.StatusCode = 401;
                httpResponse.AddHeader("WWW-Authenticate", headerValue);
                httpResponse.Flush();
                httpResponse.Close();
        }

        public string RequestAuthenticationDetails()
        {
                HttpContext httpContext = HttpContext.Current;
                HttpRequest httpRequest = httpContext.Request;

                return httpRequest.Headers["Authorization"];
        }

        public void SetUserPrinciple(string userName, string password)
        {
                HttpContext httpContext = HttpContext.Current;

                IIdentity identity = new HttpListenerBasicIdentity(userName, password);
                httpContext.User = new ClientRolePrincipal(identity);
        }

        public bool IsUserAuthenticated()
        {
                HttpContext httpContext = HttpContext.Current;

                return httpContext.User != null
                        && httpContext.User.Identity != null
                        && httpContext.User.Identity.IsAuthenticated;
        }
}

public class DictionaryUserAuthenticationStore : IUserAuthenticationStore
{
        private readonly Dictionary<string, string> _userNamePasswords = new Dictionary<string, string>();

        public bool AuthenticateUser(string userName, string password)
        {
                string storedPassword;
                return
                        _userNamePasswords.TryGetValue(userName, out storedPassword)
                        && password == storedPassword;
        }

        public void RegisterUser(string userName, string password)
        {
                _userNamePasswords.Add(userName, password);
        }
}
blog comments powered by Disqus