Authentication and Authorization in AspNetCore

Authentication and Authorization in AspNetCore

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.

  1. Authentication and Authorization in Asp.Net WebForms
  2. Authentication and Authorization in Asp.Net MVC
  3. 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.

Asp.Net Lifecycle Events 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.

AspNetCore Middleware 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

  1. AuthenticationService
  2. AuthenticationHandlerProvider
  3. 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.