Reputation: 360
I have a .net core application using Identity Server 4 that creates numerous non-standard claims for authorization in the application. Some of those non-standard claims have a direct impact on how the application behaves and are set by a database. If the user performs some action on the UI I will need to reset those claims.
So my question, using the hybrid flow if I do the renew tokens found here https://github.com/IdentityServer/IdentityServer4.Samples/blob/release/Clients/src/MvcHybrid/Controllers/HomeController.cs, will that call the user info endpoint to get an updated list of claims? So more or less, resetting the login without having to log out and log back in. Or will I need to update the Claims principal manually?
I would much rather no have to update the principal manually and let IS4 do the heavy lifting.
EDIT
Using the code above I was able to refresh the tokens and I see that it's calling IS4 and resetting the claims in the IS4 code, but it's not getting the user profile which isn't actually updating the claims on the client side. I cannot save the claims in the token since the token becomes too large for log out so I have the "GetClaimsFromUserInfoEndpoint" enabled on the options. Anyway of programmatically resetting the UserProfile?
Upvotes: 3
Views: 2653
Reputation: 360
Found it! Using the refresh tokens from the sample code you will need to manually call the UserInfo endpoint to get the updated list of claims after you authenticate the cookie but before you sign in the cookie.
var disco = await DiscoveryClient.GetAsync(this.applicationSettings.IdentityServerAuthority);
if (disco.IsError) throw new Exception(disco.Error);
var userInfoClient = new UserInfoClient(disco.UserInfoEndpoint);
var tokenClient = new TokenClient(disco.TokenEndpoint, this.applicationSettings.IdentityServerAuthorityClient, this.applicationSettings.IdentityServerAuthorityPassword);
var rt = await this.httpContext.HttpContext.GetTokenAsync("refresh_token");
var tokenResult = await tokenClient.RequestRefreshTokenAsync(rt);
if (!tokenResult.IsError)
{
var old_id_token = await this.httpContext.HttpContext.GetTokenAsync("id_token");
var new_access_token = tokenResult.AccessToken;
var new_refresh_token = tokenResult.RefreshToken;
var tokens = new List<AuthenticationToken>();
tokens.Add(new AuthenticationToken { Name = OpenIdConnectParameterNames.IdToken, Value = old_id_token });
tokens.Add(new AuthenticationToken { Name = OpenIdConnectParameterNames.AccessToken, Value = new_access_token });
tokens.Add(new AuthenticationToken { Name = OpenIdConnectParameterNames.RefreshToken, Value = new_refresh_token });
var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResult.ExpiresIn);
tokens.Add(new AuthenticationToken { Name = "expires_at", Value = expiresAt.ToString("o", CultureInfo.InvariantCulture) });
var info = await this.httpContext.HttpContext.AuthenticateAsync("Cookies");
//get the updated user profile (claims)
var response = await userInfoClient.GetAsync(new_access_token);
info.Properties.StoreTokens(tokens);
//merge the new claims with the current principal
var currentIdentity = info.Principal.Identity as ClaimsIdentity;
var distinctClaimTypes = response.Claims.Select(x => x.Type).Distinct();
foreach (var claimType in distinctClaimTypes)
{
var currentCount = currentIdentity.Claims.Count(x => x.Type == claimType);
if (currentCount > 0)
{
//remove the claims from the current
var currentClaims = currentIdentity.Claims.Where(x => x.Type == claimType).ToList();
foreach (var currentClaim in currentClaims)
{
currentIdentity.RemoveClaim(currentClaim);
}
}
//add the new claims
currentIdentity.AddClaims(response.Claims.Where(x => x.Type == claimType));
}
//update the cookies with the new principal and identity
await this.httpContext.HttpContext.SignInAsync("Cookies", info.Principal, info.Properties);
return true;
}
There is probably a way to authenticate/signin with "oidc" as well. I tried, but wasn't able to get it to work.
Upvotes: 2