Openiddict-core: Configuring ASP.Net core 2.1 using JWT bearer tokens

Created on 22 Mar 2018  路  8Comments  路  Source: openiddict/openiddict-core

Hello all,

I'm having a lot of trouble trying to configure OpenIddict to use JWT tokens for authorization. I've spent a lot of time searching, read the docs, and have reviewed this post: #485 with no luck solving the issue.

I have no issues generating tokens, but when I go to use them on my [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] attributed controllers I still get a 401 Unauthorized.

My Startup.cs:

public IServiceProvider ConfigureServices(IServiceCollection services)
{
    services
        .AddMvc()
        .AddJsonOptions(options =>
        {
            options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            options.SerializerSettings.Converters.Add(new StringEnumConverter());
        })
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

    services.AddDbContext<AuthenticationDbContext>(options =>
    {
        // Configure the context to use Microsoft SQL Server.
        options.UseSqlServer(Configuration["ConnectionStrings:AuthenticationConnection"]);
        options.UseOpenIddict();
    });

    // Register the Identity services.
    services
        .AddIdentity<ApplicationUser, ApplicationRole>(options => {
            options.Lockout.MaxFailedAccessAttempts = 5;
            options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
            options.User.RequireUniqueEmail = true;
        })
        .AddEntityFrameworkStores<AuthenticationDbContext>()
        .AddSignInManager<ApplicationSignInManager<ApplicationUser>>()
        .AddDefaultTokenProviders();

    // register custom signin manager with DI
    services.AddScoped<SignInManager<ApplicationUser>, ApplicationSignInManager<ApplicationUser>>();

    services.Configure<IdentityOptions>(options => {
        options.ClaimsIdentity.UserNameClaimType = OpenIdConnectConstants.Claims.Name;
        options.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject;
        options.ClaimsIdentity.RoleClaimType = OpenIdConnectConstants.Claims.Role;
    });

    services.AddOpenIddict(options => {
        // Register the Entity Framework stores.
        options.AddEntityFrameworkCoreStores<AuthenticationDbContext>();
        options.AddMvcBinders();

        options
            .EnableAuthorizationEndpoint("/connect/authorize")
            .EnableLogoutEndpoint("/connect/logout")
            .EnableTokenEndpoint("/connect/token")
            .EnableUserinfoEndpoint("/api/userinfo");

        options.SetAccessTokenLifetime(TimeSpan.FromMinutes(60));

        options
            .AllowAuthorizationCodeFlow()
            .AllowPasswordFlow()
            .AllowRefreshTokenFlow()
            .AllowImplicitFlow();

        // Mark the "email", "profile" and "roles" scopes as supported scopes.
        options.RegisterScopes(OpenIdConnectConstants.Scopes.Email,
                                OpenIdConnectConstants.Scopes.Profile,
                                OpenIddictConstants.Scopes.Roles);

        // Make the "client_id" parameter mandatory when sending a token request.
        //options.RequireClientIdentification();
        options.EnableRequestCaching();
        options.EnableScopeValidation();
        // not for production, use x509 certificate and .AddSigningCertificate()
        options.AddEphemeralSigningKey();
        // use jwt
        options.UseJsonWebTokens();
    });

    services.AddCors();

    JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
    JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();
    services
        .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options => {
            options.Authority = "https://localhost:44357/";
            options.Audience = "resource_server";
            options.RequireHttpsMetadata = false;
            options.IncludeErrorDetails = true;
            options.TokenValidationParameters = new TokenValidationParameters() {
                NameClaimType = OpenIdConnectConstants.Claims.Subject,
                RoleClaimType = OpenIdConnectConstants.Claims.Role
            };
        });
    // return LightInject container provider
    return Container.CreateServiceProvider(services);
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    app.UseAuthentication();

    if (env.IsDevelopment())
        app.UseDeveloperExceptionPage();
    else
        app.UseHsts();

    app.UseCors(c =>
        c.AllowAnyHeader()
        .AllowAnyMethod()
        .AllowAnyOrigin()
        .AllowCredentials()
    );

    app.UseHttpsRedirection();
    app.UseMvc();
}

My test controller:

[HttpGet]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public async Task<IActionResult> IndexAsync()
{
    UserResponse userResponse = null;
    var customer = await _customerService.GetAsync(new Customer() { UserGlobalId = new Guid("A2F75613-7B8B-4C75-AAE8-18548F1DA9AF") });
    if (customer != null)
        userResponse = _mapper.Map<Customer, UserResponse>(customer, userResponse);
    return new ApiOkResponse<UserResponse>(userResponse);
}

And the api request/responses:

Token request:

POST https://localhost:44357/connect/token
{
    "username": "[email protected]",
    "password": "my_password",
    "grant_type": "password"
}

response:

{
    "resource": "resource_server",
    "token_type": "Bearer",
    "access_token": "{ACCESS_TOKEN_VALUE}",
    "expires_in": 3600
}

Controller request:

GET https://localhost:44357/api/test
Headers:
  Bearer: {ACCESS_TOKEN_VALUE}
  Content-Type: application/json

response:
401 Unauthorized

question

Most helpful comment

Quick response! I just found the root cause, it was not the aspnet configuration at all, it was in how I was passing the bearer token. I probably misread it somewhere in all my document hunting. Hopefully this helps others, drove me nuts for about 4 hours!

I was doing:

GET https://localhost:44357/api/test
Headers:
  Bearer: {ACCESS_TOKEN_VALUE}
  Content-Type: application/json

when I should have been doing:

GET https://localhost:44357/api/test
Headers:
  Authorization: Bearer {ACCESS_TOKEN_VALUE}
  Content-Type: application/json

(note the token must be prepended with the word Bearer+[space]). Was testing using Postman.

All 8 comments

Hey,

Anything interesting in the resource server logs? The root cause should be indicated somewhere.

Quick response! I just found the root cause, it was not the aspnet configuration at all, it was in how I was passing the bearer token. I probably misread it somewhere in all my document hunting. Hopefully this helps others, drove me nuts for about 4 hours!

I was doing:

GET https://localhost:44357/api/test
Headers:
  Bearer: {ACCESS_TOKEN_VALUE}
  Content-Type: application/json

when I should have been doing:

GET https://localhost:44357/api/test
Headers:
  Authorization: Bearer {ACCESS_TOKEN_VALUE}
  Content-Type: application/json

(note the token must be prepended with the word Bearer+[space]). Was testing using Postman.

@replaysMike nice catch! :+1:

Thanks for sharing your answer.

(note the token must be prepended with the word Bearer+[space]). Was testing using Postman.

I just read your post and I was going to say you found out.

Thank you and please help me!
My error:

OpenIddict.OpenIddictProvider[0]
The token request validation process was partially skipped because the 'client_id' parameter was missing or empty.

It's not an error but an information (as indicated by the log level). If you have an issue, it's not caused by that. Please open a new thread and post your entire logs.

If you use the latest RC3 bits, don't miss https://github.com/openiddict/openiddict-core/issues/621

@PinpointTownes Thank you so much!

Seriously had the exact same error when adding the JWT to my request headers.

client.DefaultRequestHeaders.Add("Bearer", _jwt);

better be

client.DefaultRequestHeaders.Add("Authorization", "Bearer " + _jwt);

Nice!

Was this page helpful?
0 / 5 - 0 ratings