26

Please help me to understand the difference between JWT token validation from the ASP netcore application and the netcore Kestrel hosted application.

There are two applications that verifies token using the source code like below:

public static IServiceCollection AddJwtToken(this IServiceCollection services, OAuthConfig config)
{
    services.AddMvc();
    services.AddAuthorization();

    Logger.DebugFormat("AddJwtBearer authority:{0} audience:{1}", config.GetAuthority(), config.Resource);

    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options => new JwtBearerOptions
        {
            Authority = config.GetAuthority(),
            Audience = config.Resource,
    });

    return services;
}

it is pretty simple and it works well if token is being validated from the asp net core 2.2 application

// in the asp.net core
var builder = WebHost.CreateDefaultBuilder(args);
builder
        .UseStartup<Startup>()
        .ConfigureKestrel(_ => _.ConfigureEndpoints())
        .UseSerilog();

And there is another application (console) that starts the same rest service host using the UseKestrel

//in the console app
var builder = WebHost.CreateDefaultBuilder()
    .UseNLog()
    .UseKestrel(_ => _.ConfigureEndpoints())
    .UseStartup<Startup>();

the only one significant difference is that there is UseKestrel in the console via ConfigureKestrel for asp.net core.

The same source code (and configuration) is used to get token from the Azure AD. Please find it as the gist here. It is configured to get token from the https://login.microsoftonline.com/{tenant}/v2.0 provider. The same token endpoint, clientid, secret and scope values are used for both cases.

The problem is that AddJwtBearer validates the token in the asp.net core and does not in the console app. the error is

Microsoft.IdentityModel.Tokens.SecurityTokenSignatureKeyNotFoundException: IDX10501: Signature validation failed. Unable to match keys:
kid: 'BB8CeFVqyaGrGNuehJIiL4dfjzw',
token: '{"typ":"JWT","alg":"RS256","kid":"BB8CeFVqyaGrGNuehJIiL4dfjzw"}.{"aud":"2c163c99-935b-4362-ae0d-657f589f5565","iss":"https://login.microsoftonline.com/{tenantidhere}/v2.0

Why asp.net core host validates the token (for the first AddJwtBearer implementation) and console host fails?

Thank you

No Refunds No Returns
  • 7,690
  • 4
  • 29
  • 39
oleksa
  • 2,911
  • 1
  • 22
  • 44

2 Answers2

29

to solve this error I've to load keys from the openid provider as below:

Logger.DebugFormat("AddJwtBearer authority:{0} audience:{1}", config.GetAuthority(), config.Resource);

IList<string> validissuers = new List<string>()
{
    config.GetAuthority(),
};

var configManager = new ConfigurationManager<OpenIdConnectConfiguration>($"{validissuers.Last()}/.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever());

var openidconfig = configManager.GetConfigurationAsync().Result;

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, _ =>
    {
        _.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
        {
            ValidateAudience = true,
            ValidAudience = config.Resource,

            ValidateIssuer = true,
            ValidIssuers = new[] { config.GetAuthority() },

            ValidateIssuerSigningKey = true,
            IssuerSigningKeys = openidconfig.SigningKeys,

            RequireExpirationTime = true,
            ValidateLifetime = true,
            RequireSignedTokens = true,
        };

        _.RequireHttpsMetadata = false;

    });

And it started to work for both cases. But what is the difference with the old AddJwtBearer implementation and the new one (related to the keys validation)? Keys where downloaded and supplied using the IssuerSigningKeys = openidconfig.SigningKeys but why it is not loaded automatically using the .well-known/openid-configuration by the AddJwtBearer middleware ?

oleksa
  • 2,911
  • 1
  • 22
  • 44
  • I solved my issue thanks to your answer. As for your question why the signing keys not loaded automatically, I can only assumes the id server is not configured properly. In my case, the issuer uri is `http` instead of `https`, which is weird. – Rosdi Kasim Dec 23 '19 at 08:00
  • @RosdiKasim i'm loading tokens from the `https://login.microsoftonline.com` do you think they have `http://` under the hood ? – oleksa Jan 10 '20 at 07:59
  • 10
    No. After I commented on this, I totally fixed this issue by explicitly setting the MetaDataAddress like this: `options.MetadataAddress = Config.OidcAuthority + "/.well-known/openid-configuration"`. Then the validation works automatically. – Rosdi Kasim Jan 10 '20 at 08:41
  • I used Rosdi's comment. https://stackoverflow.com/questions/49694383/use-multiple-jwt-bearer-authentication/49706390#49706390 is related to what I'm trying to setup and both of these Q's and A's were key to working solution. FWIW YMMV. Comments should be taken as of date of this writing. Thank you future readers. – No Refunds No Returns Feb 12 '20 at 02:33
  • What does "config.Resource" from the OAuthConfig Type represent in your code? Is this "ValidAudience" it hydrates the user's client browser, the machine running your code, the OpenID Connect server, or something else? – bopapa_1979 Nov 11 '20 at 20:11
  • @bopapa_1979 `config.Resource` is a resource id that token sould be issued for. The `aud` token value – oleksa Nov 12 '20 at 14:22
  • @oleksa I'm sorry, but I'm still not clear on what you mean by resource. Resource as in "restful API resource," resource, resource as in "the client that was issued an OpenID Connect client_id," or what? What kind of data goes here? A URL, a unique identifier of some kind, what? I did read the documentation here as to what the "aud" represents, but it still isn't clear. The term "audience" is not defined: https://openid.net/specs/openid-connect-core-1_0.html – bopapa_1979 Nov 17 '20 at 20:00
  • @bopapa_1979 from the link you have provided `aud REQUIRED. Audience(s) that this ID Token is intended for. It MUST contain the OAuth 2.0 client_id of the Relying Party as an audience value. It MAY also contain identifiers for other audiences. In the general case, the aud value is an array of case sensitive strings` – oleksa Nov 18 '20 at 09:42
  • @bopapa_1979 Client asks to issue token for certain resource (unique string identifier) Then service can verify that this token was issued for that resource as far as I understood – oleksa Nov 18 '20 at 09:48
  • Microsoft recommends caching the keys for 24 hours. See https://docs.microsoft.com/en-us/azure/active-directory/develop/access-tokens#validating-the-signature. Does ConfigurationManager do this automatically, or does it always retrieve the latest config without caching? – Florian Winter Jan 12 '21 at 11:54
  • @FlorianWinter I do not think that `ConfigurationManager` can do such caching. However it may be done like storing `TokenValidationParameters` instance and renewing the `IssuerSigningKeys` by schedule. However it looks like overkill so please try with @RosdiKasim solution first. – oleksa Jan 12 '21 at 12:24
11

In my case, the same error was because of inadvertent use of the token received from one environment (https://dev/identity) and validated in another environment (i.e. http://local/identity).

Donut
  • 103,927
  • 20
  • 129
  • 143