Introduction
AspNetCore is an open source cross platform framework for building modern, scalable and high-performance web applications.
The traditional .NET Framework had some limitations that has been removed in AspNetCore making
it a popular framework to be adopted by the enterprises and community.
In this post we are going to understand the process of authentication and authorization in AspNetCore and see how different it is from the .NET Framework in WebApi, MVC and WebForms application.
In our previous posts we discussed the details of Authentication and Authorization in Asp.Net WebApi, 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 Asp.Net WebApi
Understanding the AspNetCore Pipeline
Before we move ahead into the authentication and authorization let us examine how a request is processed in AspNetCore.
In the traditional .NET Framework the Asp.Net Pipeline was constructed using the
HTTPModule and HTTPHandler.
HTTPModule and HTTPHandler in Asp.Net
In Asp.Net one or more HTTPModule is called while processing a request. The request was allowed to be short-circuited
just in case it failed to meet the criteria defined by HTTPModule and the request was terminated.
When one module was executed the other module was invoked for further processing of the request.
The modules were defined in the web config and was hooked into the request in the
predefined events in Asp.Net. Also the modules were defined in the order in which they were defined
in the webconfig.
Image Source - Microsoft
Similarly HTTPHandler was defined for a request and it processed the file with the specified extension.
The modules and handlers in Asp.Net is no longer the part of the AspNetCore ecosystem.
Middleware in AspNetCore
The request processing pipeline in AspNetCore is registered with the extension methods on IServiceCollection and constructed by Middlewares.
Middlewares does a job similar to the modules in Asp.Net but with more flexibility.
The restriction to hook a module into predefined event is removed with middlewares.
Middlewares can be hooked into the request pipeline with the order defined by the developer.
Unlike modules , middlewares are specified in the code and not in the config.
The middleware is invoked for each request. Each middleware either calls the next middleware
after processing or it short circuits and terminates the request by sending back the response.
Image Source - Microsoft
Authentication and Authorization Middleware
Let us now understand the methods and middleware involved in authentication and authorization in AspNetCore.
Authentication
In AspNetCore the authentication and authorization is done by the middlewares that is
configured and plugged into the AspNetCore request processing pipeline.
The code below shows a sample request processing pipeline configured with Authentication and Authorization
middleware.
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromSeconds(20);
options.Events = new CookieAuthenticationEvents()
{
OnCheckSlidingExpiration = context =>
{
// If 25% expired instead of the default 50%.
context.ShouldRenew = context.ElapsedTime > (context.Options.ExpireTimeSpan / 4);
// Don't renew on API endpoints that use JWT.
var authData = context.HttpContext.GetEndpoint()?.Metadata.GetMetadata<IAuthorizeData>();
if (authData != null && string.Equals(authData.AuthenticationSchemes, "Bearer", StringComparison.Ordinal))
{
context.ShouldRenew = false;
}
return Task.CompletedTask;
}
};
});
var app = builder.Build();
app.UseAuthentication();
app.MapGet("/", async context =>
{
if (!context.User.Identities.Any(identity => identity.IsAuthenticated))
{
var user = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, "bob") }, CookieAuthenticationDefaults.AuthenticationScheme));
await context.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, user);
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync("Hello First timer");
return;
}
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync("Hello old timer");
});
app.MapGet("/ticket", async context =>
{
var ticket = await context.AuthenticateAsync();
if (!ticket.Succeeded)
{
await context.Response.WriteAsync($"Signed Out");
return;
}
foreach (var (key, value) in ticket.Properties.Items)
{
await context.Response.WriteAsync($"{key}: {value}\r\n");
}
});
app.Run();
The AddAuthentication method above is an extension method on IServiceCollection which injects the dependencies on the services related to Authentication in AspNetCore.
Some of the services injected are
- AuthenticationService
- AuthenticationHandlerProvider
- AuthenticationSchemeProvider
The AuthenticationSchemeProvider is the main service that associates the corresponding handler to perform the Authentication.
In the example above since the authentication scheme is Cookie the instance CookieAuthenticationHandler
service is provided to Authenticate the request.
There can be more than one authentication scheme that can be registered but one of them should
be specified as the default scheme.
The required scheme can be provided in the Authorization to apply the corresponding handler. If
no scheme is provided in authorization then the default scheme would be used for authenticating the
request.
AspNetCore supports a wide range of schemes like Certificate, Cookies, Facebook,Google,
JwtBearer,MicrosoftAccount,Twitter,WsFederation etc...
Each of the authentication schemes mentioned above has their on extension methods to further configure the authentication mechanism by providing authentication options.
In the example above the AddCookie extension method is used to configure the cookie based authentication options.
The app.UseAuthentication(); adds the AuthenticationMiddleware to the specified ApplicationBuilder to enable the authentication capabilities. The code below shows the implementation of the extension method.
public static IApplicationBuilder UseAuthentication(this IApplicationBuilder app)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
app.Properties[AuthenticationMiddlewareSetKey] = true;
return app.UseMiddleware<AuthenticationMiddleware>();
}
The AuthenticationMiddleware further makes use of the AuthenticationSchemes configured above in the AddAuthentication method to handle the authentication in the request with the help of the handler associated with the scheme.
Authorization
We can configure Authorization in ApplicationBuilder ServiceCollection with just the same way
as we did for Authentication.
The AddAuthorization extension methods allows us to configure authorization policies for
the application.
The code below shows a sample configuration for Authorization.
builder.Services.AddAuthorization(options =>
options.AddPolicy("is_admin", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim("is_admin", "true");
}));
The above code adds an authorization policy named "is_admin" which requires the user to be Authenticated and the ClaimType "is_admin" should be "true".
Just like UseAuthentication() the UseAuthorization() adds the AuthorizationMiddleware
to the specified ApplicationBuilder to enable the authorization capabilities.
The code below shows the implementation of the extension method.
public static IApplicationBuilder UseAuthorization(this IApplicationBuilder app)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
VerifyServicesRegistered(app);
app.Properties[AuthorizationMiddlewareSetKey] = true;
return app.UseMiddleware<AuthorizationMiddleware>();
}
When authorizing a resource that is routed using endpoint routing, this call must appear between the calls to app.UseRouting() and app.UseEndpoints(...) for the middleware to function correctly.
Conclusion
In this post we discussed,
The request processing pipeline for .NET Framework application and AspNetCore. In AspNetCore the traditional HTTPModule and HTTPHandler is no longer available and it is replaced with a more flexible and extensible way of processing request with the help of Middleware.
We understood the concept of registering authentication service and authenticating the request with the "UseAuthentication" middleware.
Similar to authentication, the authorization and authorization policies can be registered with the help of "AddAuthorization" and "AddPolicy" extension method respectively to enable the capabilities of Authorization with the help of "UseAuthorization" middleware extension.
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 Asp.Net WebApi
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