The new OpenID Connect handler in ASP.NET Core 2 has a different (aka breaking) behavior when it comes to mapping claims from an OIDC provider to the resulting ClaimsPrincipal.
This is especially confusing and hard to diagnose since there are a couple of moving parts that come together here. Let’s have a look.
You can use my sample OIDC client here to observe the same results.
Mapping of standard claim types to Microsoft proprietary ones
The first annoying thing is, that Microsoft still thinks they know what’s best for you by mapping the OIDC standard claims to their proprietary ones.
This can be fixed elegantly by clearing the inbound claim type map on the Microsoft JWT token handler:
A basic OpenID Connect authentication request
Next – let’s start with a barebones scenario where the client requests the openid scope only.
First confusing thing is that Microsoft pre-populates the Scope collection on the OpenIdConnectOptions with the openid and the profile scope (don’t get me started). This means if you only want to request openid, you first need to clear the Scope collection and then add openid manually.
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
.AddCookie("Cookies", options =>
options.AccessDeniedPath = "/account/denied";
.AddOpenIdConnect("oidc", options =>
options.Authority = "https://demo.identityserver.io";
options.ClientId = "server.hybrid";
options.ClientSecret = "secret";
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.TokenValidationParameters = new TokenValidationParameters
NameClaimType = "name",
RoleClaimType = "role"
With the ASP.NET Core v1 handler, this would have returned the following claims: nbf, exp, iss, aud, nonce, iat, c_hash, sid, sub, auth_time, idp, amr.
In V2 we only get sid, sub and idp. What happened?
Microsoft added a new concept to their OpenID Connect handler called ClaimActions. Claim actions allow modifying how claims from an external provider are mapped (or not) to a claim in your ClaimsPrincipal. Looking at the ctor of the OpenIdConnectOptions, you can see that the handler will now skip the following claims by default:
If you want to “un-skip” a claim, you need to delete a specific claim action when setting up the handler. The following is the very intuitive syntax to get the amr claim back:
If you want to see the raw claims from the token in the principal, you need to clear the whole claims action collection.
Requesting more claims from the OIDC provider
When you are requesting more scopes, e.g. profile or custom scopes that result in more claims, there is another confusing detail to be aware of.
Depending on the response_type in the OIDC protocol, some claims are transferred via the id_token and some via the userinfo endpoint. I wrote about the details here.
So first of all, you need to enable support for the userinfo endpoint in the handler:
options.GetClaimsFromUserInfoEndpoint = true;
If the claims are being returned by userinfo, ClaimsActions are used again to map the claims from the returned JSON document to the principal. The following default settings are used here:
IOW – if you are sending a claim to your client that is not part of the above list, it simply gets ignored, and you need to do an explicit mapping. Let’s say your client application receives the website claim via userinfo (one of the standard OIDC claims, but unfortunately not mapped by Microsoft) – you need to add the mapping yourself:
The same would apply for any other claims you return via userinfo.
I hope this helps. In short – you want to be explicit about your mappings, because I am sure that those default mappings will change at some point in the future which will lead to unexpected behavior in your client applications.