Created
October 23, 2013 07:02
-
-
Save vermorel/7113813 to your computer and use it in GitHub Desktop.
Basic Authentication in ASP.NET MVC4
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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