Support multiple JWT Authorities in a .NET Core application

This post describes how you can support multiple JWT Authorities in your (ASP).NET Core application and how you can select the correct authority for each request.

This post doesn’t cover the integration with an OIDC service or proxy. It assumes there is a jwt-token on the request and your application uses this token to perform authorization based on some policies you’ve defined in your application.

Suppose your organization is upgrading to the new kid on the block: Azure B2C. During this switch you want to support your old JWT authority and the new one. It makes your application more robust and a rollback scenario is easier or even completely unnecessary.

Once you’ve seen the code, it’s pretty straightforward, but it took me some time to actually implement it and understand it. Mostly because of me lacking the raw intelligence to make sense of all the technical terms, wording, moving parts and the overall architecture of how OIDC authentication and JWT authentication play together.

For clarity’s sake I’m going to add the code in the Program.cs, but you could/should move it to it’s own IServiceCollection extension method. As you’re supposed to as the super-senior, enterprisy, abstraction-loving developer.

First, add the authentication:

var auth = services.AddAuthentication(options =>
{
    options.DefaultScheme = "AZUREB2C_OR_LEGACY";
    options.DefaultChallengeScheme = "AZUREB2C_OR_LEGACY";
});

The value of DefaultScheme (and DefaultChallengeSchem) can be ANYTHING! I didn’t know this at first. I assumed it had to be a pre-defined name. Something like JwtBearerDefaults.AuthenticationScheme. Took me some time to recover from this insight. Best is to give this a name every next developer can understand. Don’t worry about a long name. You don’t have to type it that often. Put it in a const and profit! I’ve a thing with naming. I like clear names.

Next up, we need to add/configure the 2 (or more) JWT bearers. I’m reading the JWT Authorities from the appsettings.json:

"JwtAuthorities": [
  {
    "Name": "adfs",
    "Issuer": "https://adfs.yourdomain.com/adfs"
  },
  {
    "Name": "azure-b2c-flowname",
    "Issuer": "https://account.yourdomain.com/tfp/yourtenantname.onmicrosoft.com/azure-b2c-flowname/v2.0/"
  }
],

Add the JWT Bearer config:

var jwtAuthorites = configuration.GetSection("JwtAuthorities");
foreach (var jwtAuthority in jwtAuthorites.GetChildren())
{
    var name = jwtAuthority["Name"];
    var issuer = jwtAuthority["Issuer"];
    auth.AddJwtBearer(name, options =>
    {
        options.RequireHttpsMetadata = false;
        options.SaveToken = true;
        options.Authority = issuer;
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = false,
            ValidateIssuer = false,
            ValidateAudience = false,
            ValidateLifetime = false,
            ValidateActor = false
        };
    });
}

Obviously, tweak this to your needs. I’ve just added them here for clarity.

Now we’ve added the JWT authorities, but we haven’t specified when to use which one. This can be achieved by adding a so called PolicyScheme to the Authentication pipeline. I was thrown of by this name. I’m not a native english speaker and I didn’t link the word ‘Policy’ to my intention of ‘deciding which JWT handler to use for a request`.
Please don’t judge me.

After this great realization, it was pretty straightforward. Here is the code with some explanation:

auth.AddPolicyScheme("AZUREB2C_OR_LEGACY", "AZUREB2C_OR_LEGACY", options =>
{
    var fallbackScheme = jwtAuthorites.GetChildren().First()?["Name"];
    options.ForwardDefaultSelector = context =>
    {
        string authorization = context.Request.Headers[HeaderNames.Authorization];
        if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Bearer "))
        {
            var token = authorization.Substring("Bearer ".Length).Trim();
            var jwtHandler = new JwtSecurityTokenHandler();
            return jwtHandler.ReadJwtToken(token).Claims.FirstOrDefault(c => c.Type == "tfp")?.Value ??
                   fallbackScheme;
        }
        return fallbackScheme;
    };
});

A custom policy scheme (which we are dealing with here) needs a name and a displayname. Not sure what the displayname is used for, but let’s not bother with it.

I’ve defined a fallback scheme in case the logic fails, but in theory this should never happen. One could (should?) also throw an exception as we’re dealing with a JWT token we can’t handle.

After setting the fallback scheme, we start by configuring the ForwardDefaultSelector. This is a .NET construct that is used to select the correct policy to use for the current request, based on logic we feed it.

Our logic first reads the token from the ‘Authorization’ header (HeaderNames.Authorization). If it’s not null or empty we strip of the ‘Bearer ‘ part and pass in the the raw JWT token in the JwtSecurityTokenHandler class. This class parses the token and extracts useful information. In this case we’re looking for the tfp claim in the token. TFP stands for ‘Trust framework policy’ and contains the name of the policy that was used to acquire the token in Azure B2C. We then return this name as the name of the Policy to use for this request. In this case that is the name as we configured it in the appsettings.json. So make sure that these names match! And the policy was added and configured in the previous step: AddJwtBearer (name, options =>...

And that’s all there is to it! So it reads the token from the request, looks for the tfp claim and based on that name it selects the correct policy as added to the authentication pipeline.

You logic can be different of course. You could even select a different policy based on the URL since you have access to the complete request object. But be careful: it’s a potential performance disaster waiting to happen if you put too much logic in here.

Hopefully this post has helped you to implement your own ‘multiple JWT authorities selector logic thingy’ 🙂

Resources I’ve used to write this post and to build this ‘authority selector’:

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: