Search code examples
asp.netasp.net-mvcowinkatanaidentityserver3

IdentityServer3: Some Claims not being returned from identity server


Context: I am using ASP.NET MVC with OWIN self host. Below are the rest of the configs/setup.

In my Clients in identity server (notice the AllowedScopes set):

public static class InMemoryClientSource
{
    public static List<Client> GetClientList()
    {
        return new List<Client>()
        {
            new Client()
            {
                ClientName = "Admin website",
                ClientId = "admin",
                Enabled = true,
                Flow = Flows.Hybrid,
                ClientSecrets = new List<Secret>()
                {
                    new Secret("admin".Sha256())
                },
                RedirectUris = new List<string>()
                {
                    "https://admin.localhost.com/"
                },
                PostLogoutRedirectUris = new List<string>()
                {
                    "https://admin.localhost.com/"
                },
                AllowedScopes = new List<string> {
                    Constants.StandardScopes.OpenId,
                    Constants.StandardScopes.Profile,
                    Constants.StandardScopes.Email,
                    Constants.StandardScopes.Roles
                }
            }
        };
    }
}

Here are the Scopes:

public static class InMemoryScopeSource
{
    public static List<Scope> GetScopeList()
    {
        var scopes = new List<Scope>();

        scopes.Add(StandardScopes.OpenId);
        scopes.Add(StandardScopes.Profile);
        scopes.Add(StandardScopes.Email);
        scopes.Add(StandardScopes.Roles);

        return scopes.ToList();
    }
}

In the Identity Server, here's how the server is configured. (Notice the Clients and Scopes are the ones provided above) :

var userService = new UsersService( .... repository passed here .... );

        var factory = new IdentityServerServiceFactory()
            .UseInMemoryClients(InMemoryClientSource.GetClientList())
            .UseInMemoryScopes(InMemoryScopeSource.GetScopeList());

        factory.UserService = new Registration<IUserService>(resolver => userService);

        var options = new IdentityServerOptions()
        {
            Factory = factory,
            SigningCertificate = Certificates.Load(), // certificates blah blah
            SiteName = "Identity"
        };

        app.UseIdentityServer(options);

Finally, on the client web application side, this is how auth is set up:

app.UseCookieAuthentication(new CookieAuthenticationOptions()
        {
            AuthenticationType = "Cookies"
        });

        app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions()
        {
            Authority = "https://id.localhost.com",
            ClientId = "admin",
            RedirectUri = "https://admin.localhost.com/",
            PostLogoutRedirectUri = "https://admin.localhost.com/",
            ResponseType = "code id_token token",
            Scope = "openid profile email roles",
            ClientSecret = "admin",
            SignInAsAuthenticationType = "Cookies"
        });

I have implemented a custom class for IUserService:

public class UsersService : UserServiceBase
{
    public UsersService( .... repository passed here .... )
    {
        //.... ctor stuff
    }

    public override Task AuthenticateLocalAsync(LocalAuthenticationContext context)
    {
        // var user = .... retrieved from database ..... 
        // ... auth logic ...

        if (isAuthenticated)
        {
            var claims = new List<Claim>();
            claims.Add(new Claim(Constants.ClaimTypes.GivenName, user.FirstName));
            claims.Add(new Claim(Constants.ClaimTypes.FamilyName, user.LastName));
            claims.Add(new Claim(Constants.ClaimTypes.Email, user.EmailAddress));
            context.AuthenticateResult = new AuthenticateResult(user.Id.ToString(), user.EmailAddress, claims);
        }

        return Task.FromResult(0);
    }
}

As you see, the claims are passed in this line:

context.AuthenticateResult = new AuthenticateResult(user.Id.ToString(), user.EmailAddress, claims);

When I try logging in to IdentityServer3, I can log in successfully to the client web application. HOWEVER, when I get the user claims, I don't see any identity claims. No given_name, family_name, and email claims. Screenshot below:

As you can see, there are no given_name, family_name and email claims included.

Anything I might have missed? Thanks in advance!


Solution

  • Finally found the solution for this problem.

    First, I moved the creation of claims to the overridden GetProfileDataAsync (in my UserService class). Here's my implementation of it:

    public override Task GetProfileDataAsync(ProfileDataRequestContext context)
            {
                var identity = new ClaimsIdentity();
    
                UserInfo user = null;
                if (!string.IsNullOrEmpty(context.Subject.Identity.Name))
                    user = _facade.Get(context.Subject.Identity.Name);
                else
                {
                    // get the sub claim
                    var claim = context.Subject.FindFirst(item => item.Type == "sub");
                    if (claim != null)
                    {
                        Guid userId = new Guid(claim.Value);
                        user = _facade.Get(userId);
                    }
                }
    
                if (user != null)
                {
                    identity.AddClaims(new[]
                    {
                        new Claim(Constants.ClaimTypes.PreferredUserName, user.Username),
                        new Claim(Constants.ClaimTypes.Email, user.EmailAddress)
                        // .. other claims
                    });
                }
    
    context.IssuedClaims = identity.Claims; //<- MAKE SURE you add the claims here
                return Task.FromResult(identity.Claims);
            }
    

    Make sure that we pass the claims to the "context.IssueClaims" inside the GetProfileDataAsync() before returning the task.

    And for those interested on how my AuthenticateLocalAsync() looks like:

    var user = _facade.Get(context.UserName);
                if (user == null)
                    return Task.FromResult(0);
    
                var isPasswordCorrect = BCrypt.Net.BCrypt.Verify(context.Password, user.Password);
                if (isPasswordCorrect)
                {
                    context.AuthenticateResult = new AuthenticateResult(user.Id.ToString(), user.Username);
                }
    
                return Task.FromResult(0);
    

    I raised a similar issue in IdentityServer3 GitHub project page that contains the explanation on why I encountered my issue. Here's the link: https://github.com/IdentityServer/IdentityServer3/issues/1938