Search code examples
model-view-controlleridentityserver4identityclaims

IdentityServer 4 - Adding a custom claim to a User when using Implicit grant type


I'm going through the tutorial of IdentityServer 4 where is explained how to add user authentication with OpenID Connect, it can be found here:

http://docs.identityserver.io/en/latest/quickstarts/3_interactive_login.html

Basically in this tutorial we have an MVC application with a controller action decorated with a Authorized attribute. Each time a user tries to access that action, in case he/she is not logged in, the MVC application redirects the user to Identity Server so he/she can input the login credentials. If the credentials are correct, Identity Server redirects back to the MVC application where a page with the User's credentials is shown. I've concluded the tutorial and now I want to explore a bit more by adding new claims to the token but I haven't been successful so far. In the tutorial, the scopes OpenId and Profile are added by setting the AllowedScopes on the Client configuration. I tried to do create a "age" scope and add it in the same manner, but it didn't work. Does anyone have an idea how I can do this? The code is shown bellow (commented lines are stuff I already tried).

IdentityServer setup:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    // configure identity server with in-memory stores, keys, clients and scopes
    services.AddIdentityServer()
        .AddDeveloperSigningCredential()
        .AddInMemoryIdentityResources(Config.GetIdentityResources())
        .AddInMemoryClients(Config.GetClients())
        .AddTestUsers(Config.GetUsers());
}

In memory stores config:

public class Config
{
    public static IEnumerable<Client> GetClients()
    {
        return new List<Client>()
        {
            new Client
            {
                ClientId = "mvc",
                ClientName = "MVC Client",
                AllowedGrantTypes = GrantTypes.Implicit,

                // where to redirect to after login
                RedirectUris = { "http://localhost:5002/signin-oidc" },

                // where to redirect to after logout
                PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },
                RequireConsent = false,
                //AlwaysIncludeUserClaimsInIdToken = true,
                //AlwaysSendClientClaims = true,
                //AllowAccessTokensViaBrowser = true,

                AllowedScopes = new List<string>
                {
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile,
                    "age"
                }
            }
        };
    }

    public static List<TestUser> GetUsers()
    {
        return new List<TestUser>()
        {
            new TestUser()
            {
                SubjectId = "1",
                Username = "alice",
                Password = "password",
                Claims = new List<Claim>()
                {
                    new Claim("age", "15"),
                    new Claim("name", "Alice"),
                    new Claim("website", "https://alice.com")
                }
            },

            new TestUser()
            {
                SubjectId = "2",
                Username = "bob",
                Password = "password",
                Claims = new List<Claim>()
                {
                    new Claim("age", "16"),
                    new Claim("name", "Bob"),
                    new Claim("website", "https://bob.com")
                }
            }
         };
     }

     public static IEnumerable<IdentityResource> GetIdentityResources()
     {
        return new List<IdentityResource>
        {
            new IdentityResources.OpenId(),
            new IdentityResources.Profile(),
            new IdentityResource()
            {
                DisplayName = "Age",
                Name = "age",
                UserClaims = new List<string>()
                {
                    "age"
                }
            }
        };
    }
}

MVC Application Config:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvcCore()
        .AddRazorViewEngine()
        .AddAuthorization()
        .AddJsonFormatters();

    JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

    services.AddAuthentication(options =>
    {
        options.DefaultScheme = "Cookies";
        options.DefaultChallengeScheme = "oidc";
    })
    .AddCookie("Cookies")
    .AddOpenIdConnect("oidc", options =>
    {
        options.SignInScheme = "Cookies";
        options.Authority = "http://localhost:5000";
        options.RequireHttpsMetadata = false;

        options.ClientId = "mvc";
        options.SaveTokens = true;
    });

}

MVC Application Claims Page:

<dl>
    @foreach (var claim in User.Claims)
    {
        <dt>@claim.Type</dt>
        <dd>@claim.Value</dd>
    }
</dl>

This is the result after a successful login:

sid ba7ecb47f66524acce04e321b8d2c444

sub 2

idp local

name Bob

website https://bob.com

As you can see the profile claims (name and website) show up, but the custom "age" claim does not.


Solution

  • The answer to the original question is to explicitly add which claims we want to use when setting up OpenId Connect. We neet to add the following lines inside the .AddOpenIdConnect method:

    options.Scope.Clear();
    options.Scope.Add("age");
    

    The complete Setup is shown below:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvcCore()
            .AddRazorViewEngine()
            .AddAuthorization()
            .AddJsonFormatters();
    
        JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
    
        services.AddAuthentication(options =>
        {
            options.DefaultScheme = "Cookies";
            options.DefaultChallengeScheme = "oidc";
        })
        .AddCookie("Cookies")
        .AddOpenIdConnect("oidc", options =>
        {
            options.SignInScheme = "Cookies";
            options.Authority = "http://localhost:5000";
            options.RequireHttpsMetadata = false;
            options.ClientId = "mvc";
            options.SaveTokens = true;
    
            options.Scope.Clear();
            options.Scope.Add("age");
            //Add all the claims you need
        });
    }