Basic Authorization Attribute in ASP.NET MVC
A while ago when I was coding Bonobo Git Server I needed a simple authentication that works well with git over HTTP/S. Basic access authentication is supported by the most of git major clients and therefore is exactly the mechanism that I was looking for.
How Does Basic Authentication Work?
A client requests resources that are secured with the authentication. A server replies with a 401 response which is decorated with the WWW-Authenticate attribute.
HTTP/1.1 401 Authorization Required
Server: HTTPd/1.0
Date: Sat, 27 Nov 2004 10:18:15 GMT
WWW-Authenticate: Basic realm="Secure Area"
Content-Type: text/html
Content-Length: 311
Now the client knows that the server is using this kind of authentication and is prepared to send its credentials. The username and the password are encoded to a Base64 string and appended to HTTP request as Authorization attribute.
GET /private/index.html HTTP/1.1
Host: localhost
Authorization: Basic amFrdWJnYXJmaWVsZA==
This step is the weakest spot of the authentication because the string could be easily decoded by everyone else. The only secure way to use basic authentication is combining with HTTPS.
ASP.NET MVC Implementation
The most elegant way of implementing basic authentication in ASP.NET MVC is taking advantage of AuthorizationAttribute and extending its functionality.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Text;
using System.Security.Principal;
using Bonobo.Git.Server.Security;
using Microsoft.Practices.Unity;
namespace Bonobo.Git.Server
{
public class BasicAuthorizeAttribute : AuthorizeAttribute
{
[Dependency]
public IMembershipService MembershipService { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
string auth = filterContext.HttpContext.Request.Headers["Authorization"];
if (!String.IsNullOrEmpty(auth))
{
byte[] encodedDataAsBytes = Convert.FromBase64String(auth.Replace("Basic ", ""));
string value = Encoding.ASCII.GetString(encodedDataAsBytes);
string username = value.Substring(0, value.IndexOf(':'));
string password = value.Substring(value.IndexOf(':') + 1);
if (MembershipService.ValidateUser(username, password))
{
filterContext.HttpContext.User = new GenericPrincipal(new GenericIdentity(username), null);
}
else
{
filterContext.Result = new HttpStatusCodeResult(401);
}
}
else
{
if (AuthorizeCore(filterContext.HttpContext))
{
HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null);
}
else
{
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusDescription = "Unauthorized";
filterContext.HttpContext.Response.AddHeader("WWW-Authenticate", "Basic realm=\"Secure Area\"");
filterContext.HttpContext.Response.Write("401, please authenticate");
filterContext.HttpContext.Response.StatusCode = 401;
filterContext.Result = new EmptyResult();
filterContext.HttpContext.Response.End();
}
}
}
private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}
}
}
The implementation is pretty straightforward and describes the basic access authentication behavior with a simple if statement. MembershipService is a class that provides access to user database and concrete implementation is injected with Unity.
Now we can use our new attribute and decorate actions in controllers where the authorization is required.
[BasicAuthorize]
public ActionResult SecureGetInfoRefs(String project, String service)
{
...
}
Conclusion
This article shows how easy is to set up basic authentication process in ASP.NET MVC application. Extensibility with attributes is really powerfull concept.