Skip to content

Authenticate using JWT

Install package Microsoft.AspNetCore.Authentication.JwtBearer 8.0.14

In EndpointConfigurator.cs

csharp
public static class EndpointConfigurator
{
    public static void ConfigureJwt(this WebApplicationBuilder builder)
    {
        var jwtSettings = builder.Configuration.Get<AppSettings>().Jwt;
        if (jwtSettings == null)
        {
            throw new Exception("appsettings.json missing JWT");
        }

        builder.Services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(options =>
            {
                options.SaveToken = true;
                options.RequireHttpsMetadata = false;
                options.TokenValidationParameters = new TokenValidationParameters()
                {
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidateIssuerSigningKey = true,
                    ValidateLifetime = true,
                    ValidIssuer = jwtSettings.Issuer,
                    ValidAudience = jwtSettings.Audience,
                    IssuerSigningKey =
                        new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.Secret)),
                    ClockSkew = TimeSpan.Zero,
                };
            });
    }
}


In Program.cs, call ConfigureJwt()

csharp
builder.ConfigureJwt();

builder.Services.AddControllers();

//...

app.UseAuthentication(); //order matters
app.UseAuthorization();

app.MapControllers();


In Utilities, add TokenHelper.cs

csharp
public interface ITokenHelper
{
    string GenerateToken(long userId);
    ActionTaker GetActionTaker();
}

public class TokenHelper : ITokenHelper
{
    private readonly AppSetting.JwtSetting _jwtSettings;
    private readonly ILogger<TokenHelper> _logger;
    private readonly IHttpContextAccessor _httpContextAccessor;

    public TokenHelper(ILogger<TokenHelper> logger, IConfiguration configuration,
        IHttpContextAccessor httpContextAccessor)
    {
        _jwtSettings = configuration.Get<AppSetting>().Jwt;
        _logger = logger;
        _httpContextAccessor = httpContextAccessor;
    }

    public string GenerateToken(long userId)
    {
        var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.Secret));

        var customClaims = new List<Claim> { new Claim("sub", userId.ToString()) };

        var token = new JwtSecurityToken(
            issuer: _jwtSettings.Issuer,
            audience: _jwtSettings.Audience,
            expires: DateTime.Now.AddMinutes(600),
            signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256),
            claims: customClaims
        );

        return new JwtSecurityTokenHandler().WriteToken(token);
    }

    public ActionTaker GetActionTaker()
    {
        StringValues? authorization = _httpContextAccessor.HttpContext?.Request.Headers["Authorization"];
        if (string.IsNullOrEmpty(authorization.ToString()))
        {
            _logger.LogError("TokenHelper.GetUserId - Request header doesn't have Authorization");
            throw new AuthenticationException("Unauthorized");
        }
        
        var jwtToken = authorization.ToString().Split(" ")[1];

        var validationParameters = new TokenValidationParameters
        {
            ValidateIssuer = false,
            ValidateAudience = false,
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.Secret)),
            ClockSkew = TimeSpan.Zero
        };
        
        var principal = new JwtSecurityTokenHandler().ValidateToken(jwtToken, validationParameters, out var validatedToken);
        
        var claims = ((JwtSecurityToken)validatedToken).Claims;
        
        var sub = claims.FirstOrDefault(c => c.Type == "sub");
        if (sub == null)
        {
            _logger.LogError("TokenHelper.GetUserId - Token does not have sub claim");
            throw new AuthenticationException("Unauthorized");
        }
        
        if (long.TryParse(sub.Value, out long userId) == false)
        {
            _logger.LogError("TokenHelper.GetUserId - Token does not have sub claim");
            throw new AuthenticationException("Unauthorized");
        }

        return new ActionTaker(userId);
    }
    
}


In Controllers folder, add LoginController.cs

csharp
[ApiController]
[Route("[controller]")]
public class LoginController  : Controller
{
    private readonly ITokenHelper _tokenHelper;

    public LoginController(ITokenHelper tokenHelper)
    {
        _tokenHelper = tokenHelper;
    }

    [HttpPost]
    public IActionResult Login()
    {
        return Ok(_tokenHelper.GenerateToken(1));
    }
}


In DependencyConfigurator.cs,

csharp
private static void RegisterHelpers(this WebApplicationBuilder builder)
{
    builder.Services.AddHttpContextAccessor();
    builder.Services.AddSingleton<ITokenHelper, TokenHelper>();
    //...
}


In BookController.cs, inject ITokenHelper, add [Authorize] and use _tokenHelper.GetActionTaker(); to get userId

csharp
public class BookController : Controller
{
    private readonly ILogger<BookController> _logger;
    private readonly ITokenHelper _tokenHelper;

    public BookController(ILogger<BookController> logger, ITokenHelper tokenHelper)
    {
        _logger = logger;
        _tokenHelper = tokenHelper;
    }
    [Authorize]
    [HttpPost]
    public IActionResult Create(CreateBookRequest request)
    {
        var actionTaker = _tokenHelper.GetActionTaker();
        _logger.LogInformation("BookController.Create - Request {@request}, userId {userId}", request, actionTaker.UserId);
        
        //...



Call log in to get JWT token

###

POST {{Library_HostAddress}}/login/
Content-Type: application/json

alt text

Call API without token will get 401 unauthorized

GET {{Library_HostAddress}}/fact/
Content-Type: application/json

alt text

With token

GET {{Library_HostAddress}}/fact/
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoxNzQyNjU5ODM2LCJpc3MiOiJodHRwczovL2NvZGVoZXNpdmUuZGV2LyIsImF1ZCI6Imh0dHBzOi8vY29kZWhlc2l2ZS5kZXYvIn0.zM1bFPIRSuMr4SxACLBf33O8e_3mvo15avl-zONcOOE

alt text

Released under the MIT License.