Search code examples
c#asp.netasp.net-coreopeniddict

After updating ASP.NET Core 1.1 to ASP.NET Core 2.0 ,Return statement SignIn(ticket.Principal, ticket.AuthenticationScheme);is giving 500 error


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 ---

Solution

  • 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;
    });