Flexible Access Token Validation in ASP.NET Core

The ASP.NET Core authentication system went through a couple of iterations, and is pretty good now. For API scenarios, the typical choice is the JwtBearer authentication handler, which can validate bearer JWT access tokens.

There are other access token types that you might want to use, e.g. reference tokens that get validated via introspection. ASP.NET Core does not include an authentication handler for that, but we have one.

In fact, some people decide that certain (risky/public) clients should use reference token (for revocability), whereas others should use JWTs. This was historically not easy to achieve in ASP.NET Core, because the authentication system wasn’t designed for multiple handlers for the same credential type.

I opened an issue describing the problem a long time ago, which was moved to different backlogs and then ultimately closed with wontfix.

There are a couple of ways to solve the problem – e.g. register two authentication handler, e.g. JwtBearer and the OAuth introspection handler. Give them distinct scheme names, and then create an authorization policy that invokes both.

services.AddAuthorization(options =>
{
    options.AddPolicy("tokens", p =>
        p.AddAuthenticationSchemes("jwt", "introspection"));
        p.RequireAuthenticatedUser();
});

Since this invokes both handlers, one would possibly fail, one possibly succeed. This is overhead, especially since the introspection handler will do an outbound network call. So not recommended.

To solve the problem, we created a brand new authentication handler that acts like a decorator over the JWT and introspection handler. When a token comes in, it looks at its shape, invokes some advanced machine learning that checks whether the token contains a dot – and if yes – it must be a JWT. It then internally dispatches to the JWT handler, otherwise to the introspection handler.

While this works – it has quite a bit of complexity (inspect the source code, it’s not pretty). There are other shortcomings:

  • Our handler needs a unified options object that wraps the options of the individual handler under the covers. While, again this works for most parts, you can be sure that there is someone out there that that needs some setting (or event) of some handler, that was not surfaced by our configuration.
  • It really only works for the two specific handlers we decided to wrap. What if you want to bring another handler to the mix?
  • Having dependencies on the handlers makes versioning harder.
  • More code to maintain.

Going forward, we decided that we do not want to maintain this “hack” any longer. There must be a better way. Enter policy schemes.

Policy scheme were added a while ago (I forget the exact version of ASP.NET Core). Think of them as virtual schemes. They don’t really do anything besides forwarding the various security gestures to other schemes. And while doing that, giving you enough flexibility to decide at runtime which other scheme that would be. Sounds like exactly what we need.

In essence you are providing a function that inspects the current HTTP Request, and returns the scheme name of a handler to forward the request to:

public static string SchemeSelector(HttpContext context)
{
    var (scheme, token) = GetSchemeAndCredential(context);

    if (!string.Equals(scheme, "Bearer", StringComparison.OrdinalIgnoreCase))
    {
        return null;
    }
    
    if (token.Contains("."))
    {
        return "Jwt";
    }
    else
    {
        return "Introspection";
    }
}

You would then wire up a policy scheme to the authentication builder to invoke that function.

builder.AddPolicyScheme("token", "token", policySchemeOptions =>
{
    policySchemeOptions.ForwardDefaultSelector = context =>
        SchemeSelector(context) ?? DefaultScheme;
});

With this feature, I planned for quite some time already to deprecate our dedicated handler and replace it with a policy scheme. While prototyping, Brock pointed me into a direction for an even simple solution.

It turns out that every authentication handler is capable of selectively forwarding requests to another handler. IOW – simply extend your default handler with some forwarding logic to be able to handle more token types:

services.AddAuthentication("token")

    // JWT tokens (default scheme)
    .AddJwtBearer("token", options =>
    {
        options.Authority = Constants.Authority;
        options.Audience = "resource1";

        options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" };

        // if token does not contain a dot, it is a reference token
        options.ForwardDefaultSelector = Selector.ForwardReferenceToken("introspection");
    })

    // reference tokens
    .AddOAuth2Introspection("introspection", options =>
    {
        options.Authority = Constants.Authority;

        options.ClientId = "resource1";
        options.ClientSecret = "secret";
    });

The Selector.ForwardReferenceToken is a little helper that I packaged up in the IdentityModel.AspNetCore.AccessTokenValidation nuget package.

Even when you are not using reference tokens, this is a useful technique for dynamically changing the handler at runtime.

This alone doesn’t really justify maintaining another nuget package, so we threw in two other small helpers that come in useful.

The first addition is a shortcut to create a scope-based authorization policy, e.g.:

services.AddAuthorization(options =>
{
    options.AddScopePolicy("admin", "scope1");
}

…and the other is a way to normalize incoming scope claims. This is useful if your token service uses the new JWT profile for access tokens, where the scope claim is a space delimited string. This does not play nicely with .NET claims, hence we included a little claim transformer, that turns that string into individual claims:

services.AddScopeTransformation();

HTH

This entry was posted in ASP.NET Core, OAuth. Bookmark the permalink.

6 Responses to Flexible Access Token Validation in ASP.NET Core

  1. Andriy says:

    ha-ha, I like the joke about “advanced machine learning” :))

  2. jinweijie says:

    “machine learning” <- like it :D

  3. tobiassuttmann says:

    Thanks, for the introspection library. It works seamlessly. I implemented the whole “dynamic scheme forwarding stuff” a bit different. Instead of registering a policy handler i made the jwt handler the default scheme and added a forward selector to it that could forward to the introspection handler based on `JwtSecurityTokenHandler.CanReadToken`.
    I was astonished that aspnetcore doesn´t include a similiar handler. Maybe because azure ad (b2c) can only issue self-contained jwt bearer tokens…

    Implementing the scope transform logic inside a extension method is quite usefull. So one can add this specific scope transformation logic to a existing claimstransformation. Another problem with the scope claim comes up when the token was issued by azure ad. Instead of “scope” claim type the tokens include the claim type “scp”. One possible way to solve this, is by modifying the claimstransformation to transform “scp” claims as well. Alternatively one could add a mapping “scp” -> “scope” to `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap`. I´ll be glad when someone could share his/her opinion in this regards.

  4. Kira says:

    How can we call user_info endpoint once token is validated to get more claims

Leave a comment