Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add claims to access token for identityserver3 using custom user service

Tags:

I am trying to create a custom user, authenticate them and then return the user claims into an angular application using identity server 3. I've looked over the samples, specifically the CustomUserService project.

Edit I've updated question based on progress to:

Within the starup.cs file I have the scopes, and clients loading

var idServerServiceFactory = new IdentityServerServiceFactory()
                    .UseInMemoryClients(Clients.Get())
                    .UseInMemoryScopes(Scopes.Get());
                   //.UseInMemoryUsers(Users.Get());

Then the override in UserServices.cs

public override Task AuthenticateLocalAsync(LocalAuthenticationContext context)
        {
            string hashedPassword = string.Empty;
        string salt = string.Empty;

        //pull users 
        using (IdentityModelContext ctx = new IdentityModelContext())
        {
            IIdSrvUserRepository ur = new IdSrvUserRepository(ctx);
            Users = ur.GetAll().ToList();
           }
        //salt curent password        
        var user = Users.SingleOrDefault(x => x.UserName == context.UserName);
        if (user != null)
        {
            salt = user.Salt;
            hashedPassword = IqUtils.GenerateSaltedPassword(context.Password, salt);
            if (user.UserName == context.UserName && hashedPassword == user.Password)
            {
                try
                {
                    context.AuthenticateResult = new AuthenticateResult(user.Subject, user.UserName);
                }
                catch (Exception ex)
                {
                    string msg = $"<-- Login Error: Message: {ex.Message}, Inner Exception:{ex.InnerException} --->";
                    myLogger log = new myLogger("IdentityServer");
                }
            }          
        }
        return Task.FromResult(0);
    }

Where I am having trouble is on the GetProfileDataAsync method. I cannot figure out how to add claims to the client or if I need to invoke a second client.

The application is an angular client authenticating to Idsrv. I have one client created

//Angular client authentication
                new Client 
                {
                    ClientId = "iqimplicit",
                    ClientName = "IQ Application (Implicit)",
                    Flow = Flows.Implicit, 
                    AllowAccessToAllScopes = true,
                    IdentityTokenLifetime = 300,//default value is 5 minutes this is the token that allows a user to login should only be used once
                    AccessTokenLifetime = 3600, // default is one hour access token is used for securing routes and access to api in IQ
                    RequireConsent = false,
                    RequireSignOutPrompt = true,
                    AllowedScopes = new List<string>
                    {
                       //no clue if this is correct
                        StandardScopes.Profile.Name

                    },
                    RedirectUris = new List<string>

                    { 
                        angularClient + "callback.html"
                    },  
                PostLogoutRedirectUris = new List<string>()
                {
                    //redirect to login screen
                    angularClient 
                }
                }

Following the SO posting here I was trying to add the claims to the access token returned to the client.

The scope I have defined is just for the Resource. I assume I need to create a second for identity similar to the commented out section?

            return new List<Scope>
                { 
                    StandardScopes.OpenId,
                    StandardScopes.ProfileAlwaysInclude,
                    StandardScopes.Address,  

                    new Scope
                    { 
                        Name = "appmanagement",
                        DisplayName = "App Management",
                        Description = "Allow application to management.",
                        Type = ScopeType.Resource,
                        Claims = new List<ScopeClaim>()
                        {
                            new ScopeClaim("role", false),
                            new ScopeClaim("Name", true),
                            new ScopeClaim("GivenName", true),
                            new ScopeClaim("FamilyName", true),
                            new ScopeClaim("Email", true),
                        },
                    },

                //    new Scope
                //    {
                //        Name = "iquser",
                //        DisplayName = "User",
                //        Description = "Identifies the user",
                //        Type = ScopeType.Identity,
                //        Claims = new List<ScopeClaim>()
                //        {
                //            new ScopeClaim("Name", true)
                //        }
                //    }
                };
        }
    }
}

The information I want to pass back as claims are indicated above, (name, givenname, familyname email).

Attempting to follow the comments from the previously mentioned SO postI overrode the GetProfileDataAsync which should be getting the information for the user.

  public override Task GetProfileDataAsync(ProfileDataRequestContext context)
            {
                // issue the claims for the user
                var user = Users.SingleOrDefault(x => x.Subject == context.Subject.GetSubjectId());
                if (user != null)
                {
                    if(context.RequestedClaimTypes != null)
                        try
                        {
                            if (context.RequestedClaimTypes != null)
                            {
                                List<Claim> newclaims = new List<Claim>();
                                foreach (Claim claim in context.Subject.Claims)
                                {
                                    if (context.RequestedClaimTypes.Contains(claim.Type))
                                    {
                                        newclaims.Add(claim);
                                    }
                                }
                                context.IssuedClaims = newclaims;
                            }                        
                        }
                        catch (Exception ex)
                        {    
                            string msg = $"<-- Error Getting Profile: Message: {ex.Message}, Inner Exception:{ex.InnerException} --->";
                            myLogger log = new myLogger("IdentityServer");
                        }
                }
                //return Task.FromResult(context.IssuedClaims);
                return Task.FromResult(0);
            }
        }

And this is where I am stuck While I see the claims I defined in the context.RequestedClaimTypes There is never a match in claim.Type as it always contains the base properties of the idsrv claim.

Since this is a custom database all information that is going on the claims is stored in the Users table of the database and not within a Claims table. I was trying to map from the user table the values of each field into the Claim value.

Update I've gone back and uncomment from the scope and added the following claims to an identity scopeType

new Scope
                    {
                        Name = "iuser",
                        DisplayName = "User",
                        Description = "Identifies the  user",
                        Type = ScopeType.Identity,
                        Claims = new List<ScopeClaim>()
                        {
                            new ScopeClaim(Constants.ClaimTypes.Name, alwaysInclude: true),
                            new ScopeClaim(Constants.ClaimTypes.GivenName, alwaysInclude: true),
                            new ScopeClaim(Constants.ClaimTypes.FamilyName, alwaysInclude: true),
                            new ScopeClaim(Constants.ClaimTypes.Email, alwaysInclude: true),
                        }
                    }

Initial Authentication works and on the callback I can see the scopes evaluated. However I'm still stuck on how I would now get the values from the DB applied to these scopes. This section of code is (as expected) looking for the System.Security.Claims.Claim type. This is now the stopping point in determining how to get the claim values which are properties on my user applied and returned as claims.

At this point I am redirected back to the angular app but the accesstoken object is blank for user information.

How do I go about inserting my own values into the context.IssuedClaims as claims at this point?

Thanks in advance

like image 964
rlcrews Avatar asked Sep 16 '16 15:09

rlcrews


People also ask

What is claims in IdentityServer?

IdentityServer emits claims about users and clients into tokens. You are in full control of which claims you want to emit, in which situations you want to emit those claims, and where to retrieve those claims from.

What are scopes and claims?

Simply put: Claims are assertions that one subject (e.g. a user or an Authorization Server) makes about itself or another subject. Scopes are groups of claims.


1 Answers

A couple of comments on what you seem to be trying to do.

1 You're duplicating an existing scope, namely the profile scope.

You don't need to define your own scope iuser, if all the claims you want are in a OIDC standard scopes (here: profile / email ). The claims you're declaring present in your iuser identity scope is already covered by the profile scope in the standard. So just ask for the profile scope instead, and make your client be allowed to request that scope.

Standard scopes, and what their claim conents are: http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims

2 The AllowedScopes configuration on your client configuration is a bit wrong.

Use the following constants instead.

AllowedScopes = new List<string>
{
    Constants.StandardScopes.OpenId,
    Constants.StandardScopes.Profile,
    Constants.StandardScopes.Email,
}

See also this sample config. https://github.com/IdentityServer/IdentityServer3.Samples/blob/master/source/_sharedConfiguration/Clients.cs

3 You haven't really shown how you fetch id_tokens..

.. but remember to supply your request towards idsrv that you want a id_token back including claims from the profile scope. The request towards /authorize , /userinfo or /token endpoint must in other words inlcude a scope param with a value of at least openid profile.

For example, like Brock A. does in his oidc js client library: https://github.com/IdentityServer/IdentityServer3.Samples/blob/master/source/Clients/JavaScriptImplicitClient/app.js#L18

like image 125
John Korsnes Avatar answered Sep 22 '22 16:09

John Korsnes