I recently updated my project to ASP.NET core 2.0 from ASP.NET core 1.1 .I'm using openiddict for authentication.
This is my controller which is responsible for request/response.
[HttpPost("token")]
public async Task<IActionResult> TokenAsync(OpenIdConnectRequest request)
{
try
{
var ticket = await _service.ExchangeTokenAsync(request);
return SignIn(ticket.Principal, ticket.Properties,ticket.AuthenticationScheme);;
}
catch (Exception ex)
{
return BadRequest(new OpenIdConnectResponse
{
Error = OpenIdConnectConstants.Errors.ServerError,
ErrorDescription = ex.Message
});
}
}
this return statement
return SignIn(ticket.Principal, ticket.Properties,ticket.AuthenticationScheme);
is throwing 500 internal server error. The code works perfectly up-to this return statement but while executing this return statement getting 500 internal server error.
This is my Startup of file methods.
public void ConfigureServices(IServiceCollection services)
{
var connectionString = Configuration["ConnectionStrings:ApplicationDbContext"];
services.AddEntityFrameworkNpgsql();
services.AddDbContext<ApplicationDbContext>(
opts =>
{
opts.UseNpgsql(connectionString, b => b.MigrationsAssembly("MenuSystem.Repository"));
opts.UseOpenIddict();
}
);
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
var validIssuer = Configuration["Token:Issuer"];
services.AddAuthentication()
.AddJwtBearer(cfg =>
{
cfg.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = validIssuer,
IssuerSigningKey = securityKey,
ValidateIssuer = !String.IsNullOrEmpty(validIssuer),
ValidateAudience = false,
ValidateLifetime = true,
ValidateActor = false,
ValidateIssuerSigningKey = true
};
});
services.AddOpenIddict(options =>
{
// Register the Entity Framework stores.
options.AddEntityFrameworkCoreStores<ApplicationDbContext>();
options.AddMvcBinders();
options.EnableTokenEndpoint("/api/account/token");
options.UseJsonWebTokens();
options.AllowPasswordFlow();
options.AllowCustomFlow("urn:ietf:params:oauth:grant-type:facebook_access_token");
options.AllowCustomFlow("urn:ietf:params:oauth:grant-type:google_access_token");
options.AllowCustomFlow("urn:ietf:params:oauth:grant-type:microsoft_access_token");
options.DisableHttpsRequirement();
options.AddSigningKey(securityKey);
});
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = false;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
// User settings
options.User.RequireUniqueEmail = true;
});
services.AddCors(o => o.AddPolicy("CorsPolicy", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
}));
// Add framework services.
services.AddMvc(options =>
{
options.Filters.Add(new GlobalExceptionFilter());
});
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public virtual void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, RoleManager<IdentityRole> roleManager)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseAuthentication();
app.UseCors("CorsPolicy");
app.UseMvc();
}
Here is Debug Log
info: Microsoft.AspNetCore.Mvc.SignInResult[1]
Project> Executing SignInResult with authentication scheme (ASOS) and the following principal: System.Security.Claims.ClaimsPrincipal.
Project> info: Microsoft.AspNetCore.Mvc.SignInResult[1]
Project> Executing SignInResult with authentication scheme (ASOS) and the following principal: System.Security.Claims.ClaimsPrincipal.
Project> info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Project> Executed action Project.Controllers.AccountController.TokenAsync (Project) in 15632.6224ms
Project> info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Project> Executed action Project.Controllers.AccountController.TokenAsync (Project) in 15632.6224ms
Project> fail: Microsoft.AspNetCore.Server.Kestrel[13]
Project> Connection id "0HL852SU6TGOD", Request id "0HL852SU6TGOD:00000004": An unhandled exception was thrown by the application.
Project> System.InvalidOperationException: The authentication ticket was rejected because the mandatory subject claim was missing.
Project> at AspNet.Security.OpenIdConnect.Server.OpenIdConnectServerHandler.<SignInAsync>d__6.MoveNext()
Project> --- End of stack trace from previous location where exception was thrown ---
This answer is copied from original answer https://stackoverflow.com/a/44845552
The error you're seeing is caused by the fact your ClaimsPrincipal
doesn't have the mandatory sub
claim, as indicated by the exception message
To fix that, you have two options: manually adding the sub
claim or asking Identity to use sub
as the name identifier claim
Add the sub claims to the principal returned by await _signInManager.CreateUserPrincipalAsync(user);...
// Note: while ASP.NET Core Identity uses the legacy WS-Federation claims (exposed by the ClaimTypes class),
// OpenIddict uses the newer JWT claims defined by the OpenID Connect specification. To ensure the mandatory
// subject claim is correctly populated (and avoid an InvalidOperationException), it's manually added here.
if (string.IsNullOrEmpty(principal.FindFirstValue(OpenIdConnectConstants.Claims.Subject)))
{
identity.AddClaim(new Claim(OpenIdConnectConstants.Claims.Subject, await _userManager.GetUserIdAsync(user)));
}
... or ask Identity to use sub
as the name identifier claim:
services.Configure<IdentityOptions>(options =>
{
options.ClaimsIdentity.UserNameClaimType = OpenIdConnectConstants.Claims.Name;
options.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject;
options.ClaimsIdentity.RoleClaimType = OpenIdConnectConstants.Claims.Role;
});