Introduction
Asp.Net WebApi is a framework to build RESTful services and in this post we will uncover the Authentication and Authorization
in Asp.Net WebApi.
WebApi was first released with MVC4 in 2012 and later WebApi2 was released with several new features.
It has been a popular framework to build RESTful services that can be consumed from Mobile and Desktop
Applications.
In this post we will see the details of the authentication and authorization process in Asp.Net WebApi.
In our previous posts we have gone through the details of Authentication and Authorization in Asp.Net MVC and the WebForms Application.
You can refer to my previous posts from the link below.
- Authentication and Authorization in Asp.Net WebForms
- Authentication and Authorization in Asp.Net MVC
Authentication and Authorization
In a MVC WebApplication the mode of Authentication is pretty similar to how it works in a Asp.Net WebForms Application. However with the introduction of the [Authorize] attribute in an MVC Application we are also able to apply both Authentication and Authorization to the entire application, specific controllers or selected Action Methods. The Authorize attribute internally takes care of the Authentication followed by Authorization checking the user names and roles if specified.
The authentication in MVC was specified in the webconfig eg Forms Authentication.
Forms Authentication requires a form to capture the user credentials , validate it and generate a
Cookie to be passed in the subsequent request. Forms Authentication can still be used for Asp.Net
WebApi when the API is called from inside of a MVC WebApp or else the user would be redirected to a login
page to enter the credentials and then authenticate the request.
In MVC 5 however new Filters were introduced for separating the Authentication and Authorization logic and providing the capability to extend the logic with the help of Authentication and Authorization Filters.
Now here is an interesting point. Both MVC and WebApi has the Authorize attribute but both of these
attributes have different implementation and they are from different namespace.
It is quite possible to get confused because of the same name of the attribute.
[Authorize] in MVC - namespace System.Web.Mvc [Authorize] in WebApi - namespace System.Web.Http
We have already seen the source code for [Authorize] attribute in MVC WebApp in our previous post
Notice that in MVC the Authorize attribute works on the HttpContext.
Mentioned below is the source code of Authorize attribute in the "namespace System.Web.Http" namespace
used in WebApi.
OnAuthorization Method
public override void OnAuthorization(HttpActionContext actionContext)
{
if (actionContext == null)
{
throw Error.ArgumentNull("actionContext");
}
if (!SkipAuthorization(actionContext) && !IsAuthorized(actionContext))
{
HandleUnauthorizedRequest(actionContext);
}
}
IsAuthorized Method
protected virtual bool IsAuthorized(HttpActionContext actionContext)
{
if (actionContext == null)
{
throw Error.ArgumentNull("actionContext");
}
IPrincipal principal = actionContext.ControllerContext.RequestContext.Principal;
if (principal == null || principal.Identity == null || !principal.Identity.IsAuthenticated)
{
return false;
}
if (_usersSplit.Length != 0 && !_usersSplit.Contains(principal.Identity.Name, StringComparer.OrdinalIgnoreCase))
{
return false;
}
if (_rolesSplit.Length != 0 && !_rolesSplit.Any(principal.IsInRole))
{
return false;
}
return true;
}
Notice that the methods above take the HttpActionContext as an input as compared to the [Authorize] attribute in MVC which takes
HttpContext as the input.
The User Principal in MVC was derived from HttpContext whereas the User Principal in WebApi is derived from the RequestContext of
ControllerContext.
This gives us an idea that in WebApi the Authorize attribute works per endpoint basis.
You can also have different Authentication scheme in WebApi with the use of Authentication Filters. This mean that you can have BasicAuthentication
scheme for one endpoint and have Identity based Basic or Token authentication scheme in the other endpoint.
We get a flexibility of having different authentication scheme for different controllers or action method.
This is very useful in scenarios where you have one solution that contains all the API for your organization and for different clients.
You can maintain all the different Custom Authentication Filters and apply the same on the API as needed.
Creating a custom Authentication Filter
A custom authentication filter can be created by implementing the IAuthenticationFilter. We also need to inherit from the class System.Attribute if you want your filter to be applied as an attribute on top of controller or action methods.
There are two method in IAuthenticationFilter that needs to be overriden by the implementing class.
- AuthenticateAsync - This method checks for the valid credentials in the Authorization header and sets the ErrorResult or the Principal object based the validation result. The code below outlines a sample AuthenticateAsync method implementation.
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
// 1. Look for credentials in the request.
HttpRequestMessage request = context.Request;
AuthenticationHeaderValue authorization = request.Headers.Authorization;
// 2. If there are no credentials, do nothing.
if (authorization == null)
{
return;
}
// 3. If there are credentials but the filter does not recognize the
// authentication scheme, do nothing.
if (authorization.Scheme != "Basic")
{
return;
}
// 4. If there are credentials that the filter understands, try to validate them.
// 5. If the credentials are bad, set the error result.
if (String.IsNullOrEmpty(authorization.Parameter))
{
context.ErrorResult = new AuthenticationFailureResult("Missing credentials", request);
return;
}
Tuple<string, string> userNameAndPassword = ExtractUserNameAndPassword(authorization.Parameter);
if (userNameAndPassword == null)
{
context.ErrorResult = new AuthenticationFailureResult("Invalid credentials", request);
}
string userName = userNameAndPassword.Item1;
string password = userNameAndPassword.Item2;
IPrincipal principal = await AuthenticateAsync(userName, password, cancellationToken);
if (principal == null)
{
context.ErrorResult = new AuthenticationFailureResult("Invalid username or password", request);
}
// 6. If the credentials are valid, set principal.
else
{
context.Principal = principal;
}
}
- ChallengeAsync - This method is called for every AuthenticationFilter before the creation of HTTP Response and before the
action method of the controller is called.
If the AuthenticateAsync sets an Error or failure the ChallengeAsync method sets the corresponding Www-authenticate header that indicates the scheme expected.
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
var challenge = new AuthenticationHeaderValue("Basic");
context.Result = new AddChallengeOnUnauthorizedResult(challenge, context.Result);
return Task.FromResult(0);
}
public class AddChallengeOnUnauthorizedResult : IHttpActionResult
{
public AddChallengeOnUnauthorizedResult(AuthenticationHeaderValue challenge, IHttpActionResult innerResult)
{
Challenge = challenge;
InnerResult = innerResult;
}
public AuthenticationHeaderValue Challenge { get; private set; }
public IHttpActionResult InnerResult { get; private set; }
public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response = await InnerResult.ExecuteAsync(cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
// Only add one challenge per authentication scheme.
if (!response.Headers.WwwAuthenticate.Any((h) => h.Scheme == Challenge.Scheme))
{
response.Headers.WwwAuthenticate.Add(Challenge);
}
}
return response;
}
}
Conclusion
The authentication method in WebApi is pretty much similar to that of an MVC Application however it has given us the flexibility
to have different authentication scheme for different endpoints or controllers.
This can be achieved by having a custom implementation of the IAuthenticationFilter. The IAuthenticationFilter filter has
AuthenticateAsync and ChallengeAsync to perform the authentication and notify the client with the Www-authenticate header in case
the authentication fails.
Recommendation
Thhe code snippets in this post have been taken from the official documentation from Microsoft and I would highly recommend going through
the official documentation for details.
Authentication Filters in ASP.NET Web API 2
Thank you for reading the post and see you in the next post.
Exploring ServiceProvider in .NET
Exploring ServiceDescriptor in .NET
Exploring ServiceCollection in .NET
Authentication and Authorization in AspNetCore
Authentication and Authorization in Asp.Net MVC
Understanding Authentication and Authorization in Asp.Net WebForms
Exploring AddControllers() method in AspNetCore (.NET 7)
Understanding CreateBuilder() method in AspNetCore (.NET 7)
Setup and debug microsoft aspnetcore source code