MVC5/Web API 2 introduced a new IAuthenticationFilter (as opposed the the IAuthorizationFilter we needed to dual-purpose in the past), as well as a substantial overhaul of the user model with ASP.NET Identity. Unfortunately, the documentation is abysmal, and all the blog articles focus on the System.Web.Mvc.Filters.IAuthenticationFilter
, not the System.Web.Http.Filters.IAuthenticationFilter
, which is clearly something entirely different.
We had a project where we needed to support a Basic-over-SSL authentication scheme on the ApiControllers for a mobile client, as well as Forms auth for the MVC controllers running the admin interface. We were keen to leverage the new Identity model, mostly as it appears to be a much more coherent design than the legacy hodgepodge we’d used previously. This required a fair bit of decompilation and digging, but I eventually came up with something that worked.
Below is an excerpt of the relevant parts of our BasicAuthFilter
class – it authenticates against a UserManager<T>
(which could be the default EF version) and creates a (role-less) ClaimsPrincipal
if successful.
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken) { var authHeader = context.Request.Headers.Authorization; if (authHeader == null || authHeader.Scheme != "Basic") context.ErrorResult = Unauthorized(context.Request); else { string[] credentials = ASCIIEncoding.ASCII.GetString(Convert.FromBase64String(authHeader.Parameter)).Split(':'); if (credentials.Length == 2) { using (var userManager = CreateUserManager()) { var user = await userManager.FindAsync(credentials[0], credentials[1]); if (user != null) { var identity = await userManager.CreateIdentityAsync(user, "BasicAuth"); context.Principal = new ClaimsPrincipal(new ClaimsIdentity[] { identity }); } else context.ErrorResult = Unauthorized(context.Request); } } else context.ErrorResult = Unauthorized(context.Request); } } public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken) { context.Result = new AddBasicChallengeResult(context.Result, realm); return Task.FromResult(0); } private class AddBasicChallengeResult : IHttpActionResult { private IHttpActionResult innerResult; private string realm; public AddBasicChallengeResult(IHttpActionResult innerResult, string realm) { this.innerResult = innerResult; this.realm = realm; } public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken) { var response = await innerResult.ExecuteAsync(cancellationToken); if (response.StatusCode == HttpStatusCode.Unauthorized) response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Basic", String.Format("realm=\"{0}\"", realm))); return response; } }
Note that you’ll need to use config.SuppressDefaultHostAuthentication()
in your WebApiConfig
in order to prevent redirection from unauthorised API calls.