简体   繁体   中英

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

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

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

At a first glance it looks like your angular client is not configured for requesting the resource scope iuser you are only configuring the requesting of the name identity scope. Add that scope to the list of allowed scopes of client iqimplicit and make sure to add that scope to the client configuration on your angular application.

Working through this some more I was finally able to figure out one approach for adding a claim.

Within the UserService class on the GetProfileDataAsync() I ended up being able to create and add a claim using the following.

var user = Users.SingleOrDefault(x => x.Subject == context.Subject.GetSubjectId());
            if (user != null)
            {
                    try
                    {
                       //add subject as claim
                        var claims = new List<Claim>
                        {
                         new Claim(Constants.ClaimTypes.Subject, user.Subject),
                        };
                        //get username
                        UserClaim un = new UserClaim
                        {
                            Subject = Guid.NewGuid().ToString(),
                            ClaimType = "username",
                            ClaimValue = string.IsNullOrWhiteSpace(user.UserName) ?  "unauth" : user.UserName
                        };
                        claims.Add(new Claim(un.ClaimType, un.ClaimValue));
                        //get email
                        UserClaim uem = new UserClaim
                        {
                            Subject = Guid.NewGuid().ToString(),
                            ClaimType = "email",
                            ClaimValue = string.IsNullOrWhiteSpace(user.EmailAddress) ? string.Empty : user.EmailAddress
                        };
   context.IssuedClaims = claims;

                    }
                    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);

The issue I had was more in the model. All the information which would normally be saved in a Claims store was stored on the user itself so when the profile is created it was at this point I had to pull the values from the user and create/add them as claims.

The OIDC manager in the angular client then grants me access to the claims on tehe access token and their values using the following:

 $scope.mgr.oidcClient.loadUserProfile($scope.mgr.access_token)
                    .then(function (userInfoValues) {
                        $scope.profile = userInfoValues;
                        $scope.fullName = $scope.profile.given_name + ' ' + $scope.profile.family_name;
                        checkAdmin($scope.profile.sys_admin);
                        $scope.$apply();
                    });

Note that $apply() is being used here only because this code exists inside of directive used across the application.

I'm not sure if this is the best implementation for adding claims but this approach does work. For the time being I'll hold off on marking this as the answer to see if there are better approaches.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM