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
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!
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:
when I should have been doing:
(note the token must be prepended with the word Bearer+[space]). Was testing using Postman.