Authorization Using JSON Web Token (JWT)

I needed a quick way to authorize an API request using a JSON Web Token (JWT).

  1. Generate the JWT, this is done in your Authorisation Service.

Example encoded with HmacSha256

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiJkMTEwNGQ1MC0yN2NlLTQwZDEtYjc5OS1mZTA4MDczOTU4NzUiLCJzY3JlZW5zIjoidXBsb2FkZmlsZSxmaW5kLGYxbmR0bCxyZXBvcnQsZWFzdGVyIiwibmJmIjoxNjU2MTk5MTIwLCJleHAiOjE2NTY4MDM5MjAsImlhdCI6MTY1NjE5OTEyMCwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NTAwOC8iLCJhdWQiOiJodHRwczovL2xvY2FsaG9zdDo1MDA2LyJ9.jtT-8Eq3YsqXhr-zCFuk84Jsyo4ldgepZRR1XiSmn1Y

Decoded using jwt.io the payload could look as follows, the specification on these properties is rfc7519.

1
2
3
4
5
6
7
8
9
{
"nameid": "d1104d50-27ce-40d1-b799-fe0807395875",
"screens": "uploadfile,find,f1ndtl,report,easter",
"nbf": 1656199120,
"exp": 1656803920,
"iat": 1656199120,
"iss": "https://localhost:5008/",
"aud": "https://localhost:5006/"
}

The recommended flow to generate and give your application the JWT is Authorization Code + PKCE

  1. Include the token in the request, this is done in your Application. The below is a font end application.
1
2
3
4
5
6
7
8
9
10
11
12
13
import { getBffUrl } from '../common/EnvTools';
import { getToken } from '../common/AuthTools';

export const getTeamleads = async () => {
const url = getBffUrl();
const options = {
headers: new Headers({
'Authorization': 'bearer ' + getToken(),
}),
}
const response = await fetch(`${url}/teamleads`, options);
return await response.json();
};
  1. Validate the token, this is done in the Protected Resource. In my example its my applications Backend for Frontend (BFF).

Extension method

Extension method to run as a filter in the request pipeline. This is validating the properies of the JWT along with its issuer signing key. This means the BFF needs to know what secret the Authorisation Service used to encrypt the JWT.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using PorkyBff.Application.Common;
using System.Text;

namespace PorkyBff.Application.Extensions
{
public static class JwtExtensions
{
public static void AddJwtConfig(this IServiceCollection services, IdentityTokenOptions options)
{
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,

ValidIssuer = options.Issuer,
ValidAudience = options.Audience,

IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(options.Key))
};
});
}
}
}

Register & Activate

I added this to my applications Program.cs

1
2
3
var identityTokenOptions = builder.Configuration.GetSection(IdentityTokenOptions.IdentityToken).Get<IdentityTokenOptions>();

builder.Services.AddJwtConfig(identityTokenOptions);

.Net 6 has simplified applying the authorisation check into a single extension method .RequireAuthorization

1
2
3
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers().RequireAuthorization();
  1. Now all requests to the API need to have a valid token. This can be validated with a simple ping controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace PorkyBff.Controllers
{
[ApiController]
public class PingController : ControllerBase
{
private readonly ILogger<PingController> _logger;

public PingController(ILogger<PingController> logger)
{
_logger = logger;
}

[HttpGet("/ping")]
[AllowAnonymous]
public IActionResult Get()
{
return Ok("allow anonymous response");
}

[HttpGet("/secure-ping")]
public IActionResult GetSecure()
{
_logger.LogInformation("Secure ping endpoint called at {timestamp}", DateTime.UtcNow.ToLongTimeString());
return Ok("secure response");
}
}
}

References