AspNetCore.Authentication.JwtBearer fails with No SecurityTokenValidator available for token with .net core RC2

15,834

Starting with beta5 (for ASP.NET Core RC2), the OpenID Connect server middleware no longer uses JWT as the default format for access tokens. Instead, it uses opaque tokens, encrypted by the rock-solid ASP.NET Core Data Protection stack (exactly like authentication cookies).

You have 3 options to fix the error you're seeing:

  • Use the new OAuth2 validation middleware developed to support opaque tokens (the recommended option, if your API and your authorization server are part of the same app). For that, keep the AspNet.Security.OAuth.Validation reference you have in project.json and replace app.UseJwtBearerAuthentication(...) by just app.UseOAuthValidation(). You can also remove Microsoft.AspNetCore.Authentication.JwtBearer from project.json.

  • Force the OpenID Connect server middleware to use JWT tokens by calling options.AccessTokenHandler = new JwtSecurityTokenHandler(); in the options. Note that you'll also have to call ticket.SetResources(...) to attach the appropriate audience with the JWT tokens (see this other SO post for more information).

  • Use the new introspection middleware. This option is more complex and requires implementing the ValidateIntrospectionRequest event to validate the client credentials. Only use it if you know what you're doing.
Share:
15,834
koocbor
Author by

koocbor

Updated on June 12, 2022

Comments

  • koocbor
    koocbor almost 2 years

    I'm trying to get a simple endpoint working that issues and consumes JWT tokens using AspNew.Security.OpenIdConnect.Server to issue the token and validating using Microsoft.AspNetCore.Authentication.JwtBearer.

    I can generate the token fine but trying to authenticate the token fails with the error Bearer was not authenticated. Failure message: No SecurityTokenValidator available for token: {token}

    At this point I've stripped everything out and have the following:

    project.json

    {
      "dependencies": {
        "Microsoft.AspNetCore.Mvc": "1.0.0-rc2-final",
        "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0-rc2-final",
        "Microsoft.AspNetCore.Server.Kestrel": "1.0.0-rc2-final",
        "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0-rc2-final",
        "Microsoft.Extensions.Configuration.FileExtensions": "1.0.0-rc2-final",
        "Microsoft.Extensions.Configuration.Json": "1.0.0-rc2-final",
        "Microsoft.Extensions.Logging": "1.0.0-rc2-final",
        "Microsoft.Extensions.Logging.Console": "1.0.0-rc2-final",
        "Microsoft.Extensions.Logging.Debug": "1.0.0-rc2-final",
        "AspNet.Security.OAuth.Validation": "1.0.0-alpha1-final",
        "AspNet.Security.OpenIdConnect.Server": "1.0.0-beta5-final",
        "Microsoft.AspNetCore.Authentication": "1.0.0-rc2-final",
        "Microsoft.AspNetCore.Authentication.JwtBearer": "1.0.0-rc2-final"
      },
    
      "tools": {
        "Microsoft.AspNetCore.Server.IISIntegration.Tools": {
          "version": "1.0.0-preview1-final",
          "imports": "portable-net45+win8+dnxcore50"
        }
      },
    
      "frameworks": {
        "net461": { }
      },
    
      "buildOptions": {
        "emitEntryPoint": true,
        "preserveCompilationContext": true
      },
    
      "publishOptions": {
        "include": [
          "wwwroot",
          "Views",
          "appsettings.json",
          "web.config"
        ]
      },
    
      "scripts": {
        "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
      }
    }
    

    Startup.cs methods:

    // This method gets called by the runtime. Use this method to add services to the container.
            public void ConfigureServices(IServiceCollection services)
            {
                services.AddAuthorization(options =>
                    {
                        options.AddPolicy(JwtBearerDefaults.AuthenticationScheme,
                            builder =>
                            {
                                builder.
                                AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme).
                                RequireAuthenticatedUser().
                                Build();
                            }
                        );
                    }
                );
    
                services.AddAuthentication();
                services.AddDistributedMemoryCache();
                services.AddMvc();
                services.AddOptions();
            }
    
            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
            public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
            {
                loggerFactory.AddConsole(Configuration.GetSection("Logging"));
                loggerFactory.AddDebug();
    
                var jwtOptions = new JwtBearerOptions()
                {
                    AuthenticationScheme = JwtBearerDefaults.AuthenticationScheme,
                    AutomaticAuthenticate = true,
                    Authority = "http://localhost:5000/",
                    Audience = "http://localhost:5000/",
                    RequireHttpsMetadata = false
                };
    
                jwtOptions.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>
                    (
                        metadataAddress: jwtOptions.Authority + ".well-known/openid-configuration",
                        configRetriever: new OpenIdConnectConfigurationRetriever(),
                        docRetriever: new HttpDocumentRetriever { RequireHttps = false }
                    );
    
    
                app.UseJwtBearerAuthentication(jwtOptions);
    
                app.UseOpenIdConnectServer(options =>
                {
                    options.AllowInsecureHttp = true;
                    options.AuthorizationEndpointPath = Microsoft.AspNetCore.Http.PathString.Empty;
                    options.Provider = new OpenIdConnectServerProvider
                    {
                        OnValidateTokenRequest = context =>
                        {
                            context.Skip();
                            return Task.FromResult(0);
                        },
    
                        OnGrantResourceOwnerCredentials = context =>
                        {
                            var identity = new ClaimsIdentity(context.Options.AuthenticationScheme);
                            identity.AddClaim(ClaimTypes.NameIdentifier, "[unique id]");
    
                            identity.AddClaim("urn:customclaim", "value", OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken);
    
                            var ticket = new AuthenticationTicket(
                                new ClaimsPrincipal(identity),
                                new Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties(),
                                context.Options.AuthenticationScheme);
    
                            ticket.SetScopes("profile", "offline_access");
    
                            context.Validate(ticket);
    
                            return Task.FromResult(0);
                        }
                    };
                });            
    
                app.UseMvc();
            }
    

    sending x-url-encoded POST to http://localhost:5000 with grant_type=password, username=foo, password=bar generates the expected access_token.

    I've added the [Authorize("Bearer")] attribute to the ValuesController and this is working as expected in the JwtBearerMiddlewear is invoked but I am unable to get the token to validate.

    Has anyone got this working with .net core RC2? I've got the same thing working on RC1 but have been unable to get this going.

    Thanks.