Ideal functionality: A user is logged in and authenticated to website A. They click a button, the backend looks up the ID of the account in website B from the database, then send this information to IdentityServer to create a JWT that contains the "user_id" field. This is then used to call a REST endpoint on website B and is authenticated, then the "user_id" is used to create a log in cookie which is sent back to website A. User is then redirected.
We are running IdentityServer 4, but communicating to it using IdentityServer3 as our main codebase is on .NET Framework. I've tried including the "user_id" field in the extras parameter, but this doesn't appear to do anything.
var client = new TokenClient(requestPath, CLIENT_ID, CLIENT_SECRET,
AuthenticationStyle.PostValues);
var test = new Dictionary<string, string>
{
{ "user_id", "123123" }
};
// request token
var tokenResponse = await client
.RequestClientCredentialsAsync(apiScope, test)
.ConfigureAwait(false);
I've also tried using client.RequestCustomAsync and client.RequestAsync with no luck.
I receive a token without issue, but it doesn't include the user_id information - only the normal audience, scope, expiration times, etc.
Here is what worked for me, largely following this example
In IdentityServer, create a new class UserInfoGrant that implements IExtensionGrantValidator, extracts the custom claims from the request, adds them to the claims, and then continues
public async Task ValidateAsync(ExtensionGrantValidationContext context)
{
var userId = context.Request.Raw[UserIdKey];
...
var claims = new List<Claim>
{
new Claim(UserIdKey, userId)
}
context.Result = new GrantValidationResult(sub, GrantType, claims);
}
I then added the class to the Dependency Injection
builder.AddExtensionGrantValidator<UserInfoGrant>();
I also have a class ProfileService that implements IProfileService which adds the claims to the token that is returned
public virtual async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var authenticationType = context.Subject.Identities.First().AuthenticationType;
var isCustomAuthenticationType = authenticationType.Equals(CustomGrantName,
StringComparison.OrdinalIgnoreCase);
if (isCustomAuthenticationType)
{
var claimsToAdd = context.Subject.Identities.First().Claims;
context.IssuedClaims = claimsToAdd.ToList();
}
else { ... }
This ProfileService was also added to DI
builder.AddProfileService<Helpers.ProfileService<TUser>>();
I also added the custom grant type to the client that would be using it.
Finally, in the calling code for Website A, I request the token with this:
var tokenResponse = await client.RequestTokenAsync(new TokenRequest {
Address = disco.TokenEndpoint,
ClientId = CLIENTID,
ClientSecret = CLIENTSECRET,
GrantType = "custom_grant_name",
Parameters = {
{ "scope", PROTECTED_RESOURCE_NAME },
{ "user_id", "26616" }
}
}).ConfigureAwait(false);