This article shows two possible ways of getting user claims in an ASP.NET Core application which uses an IdentityServer4 service. Both ways have advantages and require setting different code configurations in both applications.
Code: https://github.com/damienbod/AspNetCoreHybridFlowWithApi
To use OpenID Connect in an ASP.NET Core application, the Microsoft.AspNetCore.Authentication.OpenIdConnect package can be used. This needs to be added as a reference in the project.
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>netcoreapp3.0</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="3.0.0" /> </ItemGroup> </Project>
Option 1: Returning the claims in the id_token
The profile claims can be returned in the id_token which is returned after a successful authentication. The ASP.NET Core client application just needs to request the profile scope.
public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }) .AddCookie() .AddOpenIdConnect(options => { options.SignInScheme = "Cookies"; options.Authority = "https://localhost:44352"; options.RequireHttpsMetadata = true; options.ClientId = "codeflowpkceclient"; options.ClientSecret = "codeflow_pkce_client_secret"; options.ResponseType = "code"; options.UsePkce = true; options.Scope.Add("profile"); options.Scope.Add("offline_access"); options.SaveTokens = true; }); services.AddAuthorization(); services.AddRazorPages(); }
In IdentityServer4, the corresponding client configuration uses the AlwaysIncludeUserClaimsInIdToken property to include the user profile claims in the id_token. By implementing the IProfileService, any claims can be added.
With this, all claims will be returned in the id_token and can then be used in the client application. This increases the size of the token, which might be important if you add to many claims. All values will be included and available in the User.Identity context in the client application.
new Client { ClientName = "codeflowpkceclient", ClientId = "codeflowpkceclient", ClientSecrets = {new Secret("codeflow_pkce_client_secret".Sha256()) }, AllowedGrantTypes = GrantTypes.Code, RequirePkce = true, RequireClientSecret = true, AllowOfflineAccess = true, AlwaysSendClientClaims = true, UpdateAccessTokenClaimsOnRefresh = true, AlwaysIncludeUserClaimsInIdToken = true, RedirectUris = { "https://localhost:44330/signin-oidc", $"{codeFlowClientUrl}/signin-oidc" }, PostLogoutRedirectUris = { "https://localhost:44330/signout-callback-oidc", $"{codeFlowClientUrl}/signout-callback-oidc" }, AllowedScopes = new List<string> { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, IdentityServerConstants.StandardScopes.OfflineAccess, "role" } }
Option 2: Returning the claims using the UserInfo API
A second way to get the user claims is to use the OpenID Connect User Info API. The ASP.NET Core client application uses the GetClaimsFromUserInfoEndpoint property to configure this. One important difference to option 1, is that you MUST specify the claims you require using the MapUniqueJsonKey method, otherwise only the name, given_name and email standard claims will be available in the client application. The claims included in the id_token are mapped per default. This is the major difference to the first option. You must explicit define some of the standard claims you require.
public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }) .AddCookie() .AddOpenIdConnect(options => { options.SignInScheme = "Cookies"; options.Authority = "https://localhost:44352"; options.RequireHttpsMetadata = true; options.ClientId = "codeflowpkceclient"; options.ClientSecret = "codeflow_pkce_client_secret"; options.ResponseType = "code"; options.UsePkce = true; options.Scope.Add("profile"); options.Scope.Add("offline_access"); options.SaveTokens = true; options.GetClaimsFromUserInfoEndpoint = true; options.ClaimActions.MapUniqueJsonKey("preferred_username", "preferred_username"); options.ClaimActions.MapUniqueJsonKey("gender", "gender"); }); services.AddAuthorization(); services.AddRazorPages(); }
The IdentityServer4 can be configured without the AlwaysIncludeUserClaimsInIdToken set.
new Client { ClientName = "codeflowpkceclient", ClientId = "codeflowpkceclient", ClientSecrets = {new Secret("codeflow_pkce_client_secret".Sha256()) }, AllowedGrantTypes = GrantTypes.Code, RequirePkce = true, RequireClientSecret = true, AllowOfflineAccess = true, AlwaysSendClientClaims = true, UpdateAccessTokenClaimsOnRefresh = true, //AlwaysIncludeUserClaimsInIdToken = true, RedirectUris = { "https://localhost:44330/signin-oidc", $"{codeFlowClientUrl}/signin-oidc" }, PostLogoutRedirectUris = { "https://localhost:44330/signout-callback-oidc", $"{codeFlowClientUrl}/signout-callback-oidc" }, AllowedScopes = new List<string> { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, IdentityServerConstants.StandardScopes.OfflineAccess, "role" } }
Mapping the Name property for the http user context.
The User.Identity.Name property can be matched from any claim using the TokenValidationParameters. If the default value is not returned, then you need to map this explicitly.
options.TokenValidationParameters = new TokenValidationParameters { NameClaimType = "email", // RoleClaimType = "role" };
ASP.NET Core also does some magic mapping as a default. Some claims are removed, and some are added. If you want to take control of this, you can turn this off as follows:
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
Links:
https://openid.net/specs/openid-connect-core-1_0.html
https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
https://docs.microsoft.com/en-us/aspnet/core/security/?view=aspnetcore-3.0
https://tools.ietf.org/html/rfc7636
https://docs.microsoft.com/en-us/aspnet/core/razor-pages
Missing Claims in the ASP.NET Core 2 OpenID Connect Handler?
[…] User claims in ASP.NET Core using OpenID Connect Authentication (Damien Bowden) […]
[…] User claims in ASP.NET Core using OpenID Connect Authentication – Damien Bowden […]
[…] User claims in ASP.NET Core using OpenID Connect Authentication Implementing authorization in Blazor ASP.NET Core applications using Azure AD security groups Implementing User Management with ASP.NET Core Identity and custom claims […]
This is an excellent explanation!!! I was struggling to understand why some claims are not available to my logged in user with cookies authentication.