Identityserver4: InvalidOperationException: sub claim is missing

Created on 3 Jan 2018  路  19Comments  路  Source: IdentityServer/IdentityServer4

We are trying to use our Azure AD to log in with Identity server, and with SSL enabled, it keeps erroring on the ExternalLoginCallback in the AccountController, saying the sub claim is missing. However, when I decrypt the token, the Sub Claim is in there.

Asp Core 2.0
Development Environment

I've tried the suggestions in the other threads, clearing mircosoft's type mapping, but nothing seems to work? Why would it not be able to find my sub claim in the id_token?

It breaks on "HttpContext.SignInAsync" which made me believe it was something on microsoft's end, but then the error mentions it erroring on "PrincipalExtensions.GetSubjectId(IIdentity identity)".

Here's my Identity server startup.cs snippet:

` JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; // this is the default scheme to be used
options.DefaultChallengeScheme = "identity";
//options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddIdentityServerAuthentication()
.AddCookie()
.AddOpenIdConnect("identity", options =>
{

                    options.Authority = Configuration["OpenId:Authority"];
                    options.RequireHttpsMetadata = false;
                    //options.SignInScheme = "Cookies";
                    options.Scope.Add("LOSTalkerServerAPI");
                    //options.MetadataAddress =
                    //    $"{Configuration["OpenId:Authority"]}.well-known/openid-configuration";

                    options.ClientId = Configuration["OpenId:ClientId"];
                    options.ClientSecret = Configuration["OpenId:ClientSecret"];

                    options.ResponseType = "code id_token"; // this means use "hybrid flow"
                    //options.Scope = Configuration["OpenId:Scope"];

                    options.GetClaimsFromUserInfoEndpoint = true;
                    // saves the identity, access, and refresh tokens -- stored in the properties section of the cookie
                    // each token can be accessed through Microsoft.AspNetCore.Authentication namespace
                    options.SaveTokens = true;
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        NameClaimType = JwtClaimTypes.Name,
                        RoleClaimType = JwtClaimTypes.Role
                    };
                }
            );`

the client startup.cs snippet:

` public static class ServiceExtensions
{
public static IServiceCollection AddExternalIdentityProviders(this IServiceCollection services, IConfiguration config)
{
var clientId = config["ClientId"];
var tenantId = config["TenantId"];

            // configures the OpenIdConnect handlers to persist the state parameter into the server-side IDistributedCache.
            services.AddOidcStateDataFormatterCache("aad");
            services.AddAuthentication(options =>
                {
                    options.DefaultScheme = "aad"; // this is the default scheme to be used
                    options.DefaultChallengeScheme = "aad";
                })
                .AddOpenIdConnect("aad", "Azure AD", options =>
                {
                    options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
                    options.SignOutScheme = IdentityServerConstants.SignoutScheme;

                    options.Authority = $" https://login.windows.net/{tenantId}";
                    options.ClientId = clientId;
                    options.RequireHttpsMetadata = false;
                    options.ResponseType = "id_token";
                    options.CallbackPath = "/signin-oidc";
                    options.SignedOutCallbackPath = "/signout-callback-oidc";
                    options.RemoteSignOutPath = "/signout-oidc";
                    options.SaveTokens = true;
                    options.GetClaimsFromUserInfoEndpoint = true;
                    //options.Scope.Clear();
                    //options.Scope.Add("openid");

                    options.ClaimActions.MapUniqueJsonKey("sub", "sub");

                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        NameClaimType = "name",
                        RoleClaimType = "role"
                    };
                });
            return services;
        }
}`

And the externalLoginCallback Function:
`[HttpGet]
public async Task ExternalLoginCallback()
{
// read external identity from the temporary cookie
var result = await

HttpContext.AuthenticateAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme);
if (result?.Succeeded != true)
{
throw new Exception("External authentication error");
}

        // lookup our user and external provider info
        var (user, provider, providerUserId) = FindUserFromExternalProvider(result);

        var claims = result.Principal.Claims;
        var additionalClaims = new List<Claim>();


        // if the external system sent a session id claim, copy it over
        var sid = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.SessionId);
        if (sid != null)
        {
            additionalClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value));
        }

        // if the external provider issued an id_token, we'll keep it for signout
        AuthenticationProperties props = null;
        var id_token = result.Properties.GetTokenValue("id_token");
        if (id_token != null)
        {
            props = new AuthenticationProperties();
            props.StoreTokens(new[] { new AuthenticationToken { Name = "id_token", Value = id_token } });
        }
        var localSignInProps = new AuthenticationProperties();
        ProcessLoginCallbackForOidc(result, additionalClaims, props);
        //ProcessLoginCallbackForWsFed(result, additionalClaims, props);
        //ProcessLoginCallbackForSaml2p(result, additionalClaims, props);

        // issue authentication cookie for user
        await _events.RaiseAsync(new UserLoginSuccessEvent(provider, user.UserId.ToString(), providerUserId, true));
        await HttpContext.SignInAsync(providerUserId, user.Email, provider, props, additionalClaims.ToArray());

        var returnUrl = result.Properties.Items["returnUrl"];
        if (_interaction.IsValidReturnUrl(returnUrl) || Url.IsLocalUrl(returnUrl))
        {
            return Redirect(returnUrl);
        }

        return Redirect("~/");
    }`
core question

Most helpful comment

All 19 comments

Can you prepare the smallest sample that consistently reproduces this issue and make it available for us to look at? We've had this issue reported before but no one has been able to provide us a repro to consistently illustrate the issue.

Thank you for the quick response! Since we are updating from Core 1 to 2. I am currently creating a testing one to see if a fresh Identity server demo download from the version 2 release would fix the issue. Once I get it up, I'll upload it to you guys.

TesttoSendtoIDServ.zip

Here is the Test project I created. The only thing you should have to do is go into the appsettings.json in the IdentityServer project, and replace the tenantID with the Azure tenant ID you're using, and the clientId with the app registered in azure in azure. Then, it should run and give the error.

I kind of threw this together real quick so please excuse is minor configuration issues. The error is the same consistently though. Thank you for your time.

Thank you. I had that in there before, but saew another demo in this repo with it removed. Now, I am getting back to my client with an error 400 saying the size of the request headers is too long. I've gotten this before, and saw another issue with a one liner that was supposed to fix it, but I couldn't get it to work? Any suggestions?

Also, I tried chrome, which keeps recreating the nonce until it throws the error. Then, tried in IE which appears to do the same thing except it goes back to the identity server login instead of throwing the error

Hello Brock and Brian.

I was having the same issue (InvalidOperationException: sub claim is missing) until I found this discussion between the two of you.

I then implemented the missing configuration pieces Brock suggested and it started working for me.
However, what I now realize is that whenever I log into Google to be authenticated and then log back out, if I attempt to log back in I am automatically logged back in without re-entering my credentials. It seems as if the authentication cookies are not getting deleted when I logout. How can I prevent this from happening.

Thanks.

Google does not support the signout protocol.

Keep their business model in mind - they can only track you/show the right ads when you are logged in.

All set on this issue -- can we close?

Thank you Brock.

Regards,

@Brian-Elliott -- all set?

this is a deeper problem than Google's biz model. The sign in with the OP can be supporting a wide variety of clients. It is not rational to allow a client to sign out that impacts the OP and other clients.

Thanks everyone.
This blog helped me a lot.

I got the logout feature to work as I wanted it to using the following code in the Logout action:

var myCookies = Request.Cookies.Keys;
foreach (string cookie in myCookies)
{
Response.Cookies.Delete(cookie, new Microsoft.AspNetCore.Http.CookieOptions()
{
//Domain = "http://localhost:5000"

                    Expires = DateTime.Now.AddDays(-1)
                });
            }

Source: https://stackoverflow.com/questions/6403978/how-to-remove-all-current-domain-cookies-in-mvc-website

This issue, yes. We can close. However, we still were not able to get the code working in production with an SSL cert for our active directory. Now, I'm getting an error saying It can't convert JToken to JArray because the "amr" claim is an array. Any advice on that would be appreciated, but our Identity Server issues have pushed back our time to release far too much so we hooked up our login directly to our AD. Identity server is on the backburner for now. Sorry for the delayed response and thank you for your help!

we have the same issue. Actually we have a version of identity running on production and if we take the same code version and just do re-build, then suddenly we start getting this issue. What could be the reason? Nuget packages updated, or docker image?

UPDATE
Updating nuget packages to latest version fixed the problem

@brockallen @leastprivilege Urls like:
https://github.com/IdentityServer/IdentityServer4/blob/dev/src/Host/Startup.cs#L86-L87 does not work anymore and can't get help you guys suggest us for. Do we have any chance we can update them?

I see that Brian-Elliot ended up (more or less) getting his issue resolved by amending his configuration settings in Startup.cs. However, the link provided is no longer a valid link. As I'm currently getting the same error as he was, could someone tell me what configuration options/settings were missing and necessary?

Error:

"InvalidOperationException: sub claim is missing
IdentityServer4.Extensions.PrincipalExtensions.GetSubjectId(IIdentity identity) in PrincipalExtensions.cs, line 80"

Startup.cs:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.AzureAD.UI;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;


namespace AuthorizationServer
{
    public class Startup
    {
        public IConfiguration Configuration { get; }
        public IHostingEnvironment Environment { get; }
        private string certificatePath;

        public Startup(IConfiguration configuration, IHostingEnvironment environment)
        {
            Configuration = configuration;
            Environment = environment;            
        }
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            string connectionString = Configuration.GetConnectionString("DefaultConnection");            
            var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
            certificatePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase) + "\\Certificate.pfx";
            certificatePath = certificatePath.Replace("file:\\", "");            

            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            var builder = services.AddIdentityServer()
                .AddSigningCredential(new X509Certificate2(certificatePath, Configuration["CertPass"]))
                .AddTestUsers(Config.GetUsers())
                // this adds the config data from DB (clients, resources)
                .AddConfigurationStore(options =>
                {
                    options.ConfigureDbContext = b =>
                        b.UseSqlServer(connectionString,
                            sql => sql.MigrationsAssembly(migrationsAssembly));
                })
                // this adds the operational data from DB (codes, tokens, consents)
                .AddOperationalStore(options =>
                {
                    options.ConfigureDbContext = b =>
                        b.UseSqlServer(connectionString,
                            sql => sql.MigrationsAssembly(migrationsAssembly));

                    // this enables automatic token cleanup. this is optional.
                    options.EnableTokenCleanup = true;
                })
                .AddJwtBearerClientAuthentication()
                .AddAppAuthRedirectUriValidator()
                .AddMutualTlsSecretValidators();

            services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
            .AddAzureAD(options => Configuration.Bind("AzureAd", options));

            services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
            {
                options.Authority = options.Authority + "/v2.0/";         // Microsoft identity platform

                options.TokenValidationParameters.ValidateIssuer = false; // accept several tenants (here simplified)
            });

            services.AddMvc(options =>
            {
                var policy = new AuthorizationPolicyBuilder()
                     .RequireAuthenticatedUser()
                     .Build();
                options.Filters.Add(new AuthorizeFilter(policy));
            })
            .SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_2);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseHsts();
            app.UseHttpsRedirection();

            // uncomment if you want to support static files
            app.UseStaticFiles();

            app.UseIdentityServer();

            // uncomment, if you want to add an MVC-based UI
            app.UseMvcWithDefaultRoute();

        }
    }
}

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings