Skip to main content

ELL Blog

ASP.NET Core JWT Authorization

Don’t feel like explaining my code, so just copy and modify it for yourself as I can assure you it’s one of the cleanest you can find.

I removed my login handling code since you can do that yourself. My login code was send login code to email via postmark and verify it. I was also using a MongoDB database.

The project is called “SttApi” so replace that with your own project name.

appsettings.json
{
    ...
    "Jwt": {
        "Key": "Generate a key using Python secrets.token_hex(64) or similar",
        "Issuer": "https://localhost:44355/",           // url of the project, am using development settings
        "Audience": "https://localhost:44355/"
    }
}
Program.cs
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;

//  ---------------- OTHER CODE ----------------

public static void Main(string[] args) {
        var builder = WebApplication.CreateBuilder(args);

        //  ---------------- OTHER CODE ----------------

        // Swagger UI Authorization
        builder.Services.AddSwaggerGen(option => {
            option.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme {
                In = ParameterLocation.Header,
                Description = "Please enter a valid token",
                Name = "Authorization",
                Type = SecuritySchemeType.Http,
                BearerFormat = "JWT",
                Scheme = "Bearer"
            });
            option.AddSecurityRequirement(new OpenApiSecurityRequirement{{
                        new OpenApiSecurityScheme { Reference = new OpenApiReference {
                                Type=ReferenceType.SecurityScheme,
                                Id="Bearer"
                            }},
                        Array.Empty<string>()
                    }
                });
        });

        //JWT Authentication
        builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options => {
            options.TokenValidationParameters = new TokenValidationParameters {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                ValidIssuer = builder.Configuration["Jwt:Issuer"],
                ValidAudience = builder.Configuration["Jwt:Audience"],
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
            };
        });
    //  ---------------- OTHER CODE ----------------
    var app = builder.Build();
    //  ---------------- OTHER CODE ----------------
}
//  ---------------- OTHER CODE ----------------
AuthController.cs
// Controllers/AuthController.cs
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using SttApi.Models;
using SttApi.Services;

// Controller for handling user signup and login with code sent to email
// https://learn.microsoft.com/en-ca/aspnet/core/tutorials/first-mongo-app?WT.mc_id=dotnet-35129-website&view=aspnetcore-7.0&tabs=visual-studio
// https://learn.microsoft.com/en-ca/aspnet/core/tutorials/first-web-api?view=aspnetcore-7.0&tabs=visual-studio

namespace SttApi.Controllers;

[ApiController]
[Route("[controller]/[action]")]
public class AuthController : ControllerBase {
    private readonly SigningCredentials credentials;
    private readonly string jwtIssuer;
    private readonly string jwtAudience;
    private readonly JwtSecurityTokenHandler jwtSecurityTokenHandler;

    public AuthController(IConfiguration config) {
        jwtIssuer = config["Jwt:Issuer"];
        jwtAudience = config["Jwt:Audience"];
        jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["Jwt:Key"]));
        credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
    }

    [HttpPost]
    public string Login() {
        // LOGIN VERIFICATION CODE GOES HERE
        //  in general you will have to add a RequestBody parameter (just a Class)
        //  and you will need to make the return type another class
        // you will also most likely need to return Task<ResponseType> because of await/async usages
        //  and JsonWebToken will be just a field of that type
        return GenerateToken("email");
    }

#if DEBUG
    [HttpGet]
    [Authorize(Roles = "User")]
    public string TestAuthorization() {
        var email = User.FindFirstValue(ClaimTypes.Email);
        return $"Your email is {email}";
    }
#endif

    private string GenerateToken(string email) {
        var claims = new[] { new Claim(ClaimTypes.Email, email), new Claim(ClaimTypes.Role, "User") };
        var token = new JwtSecurityToken(jwtIssuer, jwtAudience, claims, signingCredentials: credentials);
        // client needs to save JWT as well incldue it in the Authorization Bearer Token header of subsequent requests
        return jwtSecurityTokenHandler.WriteToken(token);
    }
}

DO NOT USE JwtRegisteredClaimNames AS YOU WILL SPEND OVER AN HOUR DEBUGGING ISSUES!!

Gate-keeping Routes and Reading User Claims

I’m making this separate because it’s a common issue and the following way is the best way to get a claim. You can use [Authorize(Roles = "...")] in order to gate keep a single route or all routes of a controller (put the macro before the controller in the latter case).

This is a method I have in my auth controller for faster debugging since I’m doing backend and frontend for a new project.

#if DEBUG
    [HttpGet]
    [Authorize(Roles = "User")]
    public string TestAuthorization() {
        // JwtRegisteredClaimNames
        var email = User.FindFirstValue(ClaimTypes.Email);
        return $"Your email is {email}";
    }
#endif