Skip to content

Instantly share code, notes, and snippets.

@vermorel
Created October 23, 2013 07:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vermorel/7113813 to your computer and use it in GitHub Desktop.
Save vermorel/7113813 to your computer and use it in GitHub Desktop.
Basic Authentication in ASP.NET MVC4
using System;
using System.Linq;
using System.Net;
using System.Security.Principal;
using System.Text;
using System.Web;
using System.Web.Mvc;
namespace ClientUI
{
// Based on an original implementation:
// http://cacheandquery.com/blog/2011/03/customizing-asp-net-mvc-basic-authentication/
// Refactored the HTTP redirection behavior to allow combining Basic Auth
// with 'Forms' authentication in ASP.NET.
// TODO: implement the credential validation (i.e. validating username and password).
public class HttpBasicUnauthorizedResult : HttpUnauthorizedResult
{
// the base class already assigns the 401.
// we bring these constructors with us to allow setting status text
public HttpBasicUnauthorizedResult() : base() { }
public HttpBasicUnauthorizedResult(string statusDescription) : base(statusDescription) { }
public override void ExecuteResult(ControllerContext context)
{
if (context == null) throw new ArgumentNullException("context");
base.ExecuteResult(context);
// this is really the key to bringing up the basic authentication login prompt.
// this header is what tells the client we need basic authentication
context.HttpContext.Response.AddHeader("WWW-Authenticate", "Basic");
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
// suppress the HTTP redirection that normally comes with ASP.NET MVC
context.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;
}
}
public class BasicAuthAttribute : AuthorizeAttribute
{
bool _RequireSsl = true;
public bool RequireSsl
{
get { return _RequireSsl; }
set { _RequireSsl = value; }
}
private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null) throw new ArgumentNullException("filterContext");
if (!Authenticate(filterContext.HttpContext))
{
// HttpBasicUnauthorizedResult inherits from HttpUnauthorizedResult and does the
// work of displaying the basic authentication prompt to the client
filterContext.Result = new HttpBasicUnauthorizedResult();
}
else
{
// AuthorizeCore is in the base class and does the work of checking if we have
// specified users or roles when we use our attribute
if (AuthorizeCore(filterContext.HttpContext))
{
HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
}
else
{
// auth failed, display login
// HttpBasicUnauthorizedResult inherits from HttpUnauthorizedResult and does the
// work of displaying the basic authentication prompt to the client
filterContext.Result = new HttpBasicUnauthorizedResult();
}
}
}
// from here on are private methods to do the grunt work of parsing/verifying the credentials
private bool Authenticate(HttpContextBase context)
{
if (_RequireSsl && !context.Request.IsSecureConnection && !context.Request.IsLocal) return false;
if (!context.Request.Headers.AllKeys.Contains("Authorization")) return false;
var authHeader = context.Request.Headers["Authorization"];
IPrincipal principal;
if (TryGetPrincipal(authHeader, out principal))
{
HttpContext.Current.User = principal;
return true;
}
return false;
}
private bool TryGetPrincipal(string authHeader, out IPrincipal principal)
{
var creds = ParseAuthHeader(authHeader);
if (creds != null)
{
if (TryGetPrincipal(creds[0], creds[1], out principal)) return true;
}
principal = null;
return false;
}
private string[] ParseAuthHeader(string authHeader)
{
// Check this is a Basic Auth header
if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Basic")) return null;
// Pull out the Credentials with are seperated by ':' and Base64 encoded
var base64Credentials = authHeader.Substring(6);
var credentials = Encoding.ASCII.GetString(Convert.FromBase64String(base64Credentials)).Split(new[] { ':' });
if (credentials.Length != 2 ||
string.IsNullOrEmpty(credentials[0]) ||
string.IsNullOrEmpty(credentials[0])) return null;
// Okay this is the credentials
return credentials;
}
private bool TryGetPrincipal(string userName, string password, out IPrincipal principal)
{
// this is the method that authenticates against my repository (in this case, hard coded)
// you can replace this with whatever logic you'd use, but proper separation would put the
// data access in a repository or separate layer/library.
//UserBase user = Repository.Authenticate(userName, password);
string user = null; // TODO: add the credential validation
if (user != null)
{
// once the user is verified, assign it to an IPrincipal with the identity name and applicable roles
principal = new GenericPrincipal(new GenericIdentity(null /* name*/), null /* roles */);
return true;
}
else
{
principal = null;
return false;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment