(C# ASP.NET Core) HandleAuthenticateAsync and Basic Authentication in WebApi

The recommended way of implementing a custom authentication scheme is by deriving a class from AuthenticationHandler and implementing the HandleAuthenticateAsync function. This makes the code systematic because the authentication and header parsing code now moves to a dedicated class. This approach also allows us to include multiple authentication schemes in the same project - for example you might want one web api to be authorized through basic authentication, and another one through a JWT based authentication, and yet your razor pages through a cookie based authentication. In this tutorial we implement the basic authentication scheme.
(Rev. 31-Oct-2024)

Categories | About |     |  

Parveen,

Deriving from AuthenticationHandler

I will come to the code straightway because this is a mere re-write of the basic authentication scheme that we discussed in the previous tutorial. You might want to go through the previous tutorial for a recap.

The source code for the project can be obtained from the downloads attached to this video. Open the solution explorer and locate the file BasicAuthHandler.cs. Double click to open this file.


// BasicAuthHandler.cs class 

using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;

public class BasicAuthHandler : 
        AuthenticationHandler<AuthenticationSchemeOptions>
{
    // override 
    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        // parse the header 
        if (
            AuthenticationHeaderValue.TryParse(
                Request.Headers["Authorization"], out var authValue)
            &&
            ("basic".Equals(authValue.Scheme, 
                    StringComparison.OrdinalIgnoreCase))
        )
        {
            String []uidPwd = Encoding.UTF8.GetString(
                Convert.FromBase64String(authValue.Parameter ?? ""))
                       .Split(":");

            // check userid password against database 
            // and get the role of the user 
            if("hoven".Equals(uidPwd[0]) && "1234".Equals(uidPwd[1]))
            {
                // construct claims principal 
                var claims = new List<Claim>();
                    claims.Add(new Claim(ClaimTypes.Name, uidPwd[0]));
                    claims.Add(new Claim(ClaimTypes.Role, "Admin"));
                    
                // construct claims principal 
                var principal = 
                        new ClaimsPrincipal(new ClaimsIdentity(claims) );

                var authTicket = 
                        new AuthenticationTicket(principal, Scheme.Name);

                return Task.FromResult(
                        AuthenticateResult.Success(authTicket));
            }
        }

        Response.Headers.Add(
                    "WWW-Authenticate", "Basic realm=web api access");

        return Task.FromResult(
                    AuthenticateResult.Fail("invalid credentials"));
    }

    // ctor 
    public BasicAuthHandler(
            IOptionsMonitor<AuthenticationSchemeOptions>
            options,
            ILoggerFactory logger,
            UrlEncoder encoder,
            ISystemClock clock) 
            : 
            base(options, logger, encoder, clock) 
    { 
    }
}

At the start we have the various namespace directives. You can always have a look at them if you get compilation errors when you write your own code.

The class BasicAuthHandler has been derived from AuthenticationHandler. This class contains most of the boilerplate code - and so the only thing that needs to be done now is override the HandleAuthenticateAsync function as you see here.

The if condition parses the authorization header, and checks if the scheme is basic authentication.

The header is then base64 decoded, and the user-id and password segregated from the colon separated string.

After that a database or identity based check of the credentials can be made, and an authentication ticket be created, and a success result sent.

If some failure occurs, then a www-authenticate header is added to the response stream and a failure result sent. We have discussed www-authenticate header in the previous tutorial, so you can have a look at that, in case you find it a bit new.

This is all we need to do.

Video Explanation (see it happen!)

Please watch the following youtube video:

The Program.cs File

And now we can examine the program.cs file.

Open the solution explorer, and locate the program.cs file. Double-click and open it!


// Program.cs file 

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using MyRazorApp.Utility;

var builder = WebApplication.CreateBuilder();

builder.Services.AddRazorPages();

const String BASIC_SCHEME_NAME = "Basic";

// register authentication 
builder.Services.AddAuthentication()
    .AddScheme<AuthenticationSchemeOptions, BasicAuthHandler>
        (BASIC_SCHEME_NAME, null);

// register authorization 
builder.Services.AddAuthorization();

var app = builder.Build();

// add middleware 
app.UseAuthentication();

app.UseAuthorization();

app.MapRazorPages();

// decorate with [Authorize] 
app.MapGet("/token", 
[Authorize(AuthenticationSchemes =BASIC_SCHEME_NAME)]() =>
{
    return Results.Ok(new { access_token = "response from the server" });
});

app.Run();

Let's see the code line by line. The first few lines are the usual boilerplate code. After that we register the authentication services. Then we have the AddScheme method which connects the BasicAuthHandler class that we added just now.

And next we register the authorization services, and then the Authentication and Authorization middleware are added to the request pipeline.

Finally, our GET WebApi is now decorated with the Authorize attribute. This handler for this api will execute only if authentication succeeds. Look! how neat is the web api now.

Run the Project

Add a breakpoint in the web api handler. This will help us determine the flow of the program.

Now run the project and make a request to the /token end point. The browser presents a credentials box.

Type wrong credentials and hit enter. We verify that the breakpoint in the api handler is not hit, and the credentials box appears again.

Next enter the correct credentials - userid = hoven and password = 1234. Hit the enter.

We verify that the breakpoint is hit now, and the web api responds with a 200 OK response. Thanks!


This Blog Post/Article "(C# ASP.NET Core) HandleAuthenticateAsync and Basic Authentication in WebApi" by Parveen is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.