ASP.NET Core - Custom Token Authentication
Tags: dotnet, aspnet, authentication
Published: 2023-02-21 02:15:27

This is a quick reference on how to create a custom token authentication. 

For a simple demonstration, we will check the header of the request if it contains our token.

**Warning!** In real world, using plain text as your token is not the proper way to secure your application.

1. Create custom authentication scheme options and handler:
    ```cs
    using Microsoft.AspNetCore.Authentication;

    public class MyAuthenticationOptions :         
        AuthenticationSchemeOptions
    {
        public const string DefaultScheme = "MyAuthenticationScheme";
        public string TokenHeaderName { get; set; } = "MyToken";
    }
    ```
    For authentication handler:
    ```cs
    using Microsoft.AspNetCore.Authentication;
    using Microsoft.Extensions.Options;
    using System.Security.Claims;
    using System.Text.Encodings.Web;

    public class MyAuthenticationHandler :         
        AuthenticationHandler<MyAuthenticationOptions>
    {
        public MyAuthenticationHandler         
            (IOptionsMonitor<MyAuthenticationOptions> options, 
            ILoggerFactory logger, UrlEncoder encoder, 
            ISystemClock clock)
            : base(options, logger, encoder, clock) 
        { }
       protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            //check header first
            if (!Request.Headers 
                .ContainsKey(Options.TokenHeaderName))
            {
                return AuthenticateResult.Fail($"Missing header: {Options.TokenHeaderName}");
            }

            //get the header and validate
            string token = Request 
                .Headers[Options.TokenHeaderName]!;

            //usually, this is where you decrypt a token and/or lookup a database.
            if(token!= "supersecretecode")
            {
                return AuthenticateResult
                    .Fail($"Invalid token.");
            }
            //Success! Add details here that identifies the user
            var claims = new List<Claim>()
            {
                new Claim("FirstName", "Juan")
            };

            var claimsIdentity = new ClaimsIdentity
                (claims, this.Scheme.Name);
            var claimsPrincipal = new ClaimsPrincipal 
                (claimsIdentity);

            return AuthenticateResult.Success
                (new AuthenticationTicket(claimsPrincipal, 
                this.Scheme.Name));
        }
    }
    ```
2. Register the service in DI:
    ```cs
    builder.Services.AddAuthentication
        (MyAuthenticationOptions.DefaultScheme)
        .AddScheme<MyAuthenticationOptions, MyAuthenticationHandler>
        (MyAuthenticationOptions.DefaultScheme, 
            options => { });
    ```
    This does the same thing as `AddJwtBearer` and `AddCookie`, you can create an extension method if you like.

3. Add authentication to the pipeline. Make sure it comes before authorization and endpoints.
    ```cs
    app.UseAuthentication();
    app.UseAuthorization();

    app.MapControllers();
    ``` 
4. You can now set your endpoint to accept only authenticated users.
    ```cs
    [Authorize]
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController
    ```
    You can also access the claims you set in your authentication handler via `User` in your controllers:
    ```cs
    var claim = User.Claims.Where(c => c.Type == "FirstName").FirstOrDefault();
    ```
    In other classes, you can inject `ClaimsPrincipal` to access the user. Register in DI where to get the `ClaimsPrincipal` when required:
    ```cs
    builder.Services.AddHttpContextAccessor();
    builder.Services.AddTransient<ClaimsPrincipal>(s => s.GetService<IHttpContextAccessor>().HttpContext.User);
    ```
    Then in your class, add `ClaimsPrincipal` to your constructor:
    ```cs
    public class MyService
    {
        public MyService(ClaimsPrincipal user) 
        { 
    ```