如何在登出时获取持久化的id_token?(Okta+.NET6+IS4场景)
我当前使用Okta、.NET 6和Identity Server 4(计划升级到6.0),需要用id_token完成Okta的登出操作,但登出时该值始终为null。登录时已确认id_token被添加到Claims中,但登出时context.HttpContext.User.FindFirst("id_token")和context.Properties.GetTokenValue("id_token")都返回null,且此时用户声明中仅存在idp: local。
核心原因
登出时的HttpContext.User是Identity Server的本地用户身份,而非Okta外部登录的身份;同时手动存储token的逻辑与SaveTokens=true的自动存储冲突,且未关联到外部登录的认证上下文。
解决方案
1. 移除手动存储token的冗余代码
已配置options.SaveTokens = true,OpenIdConnect中间件会自动将id_token、access_token等保存到外部登录Cookie(idsrv.external)的AuthenticationProperties中,无需手动调用StoreTokens:
private Task OnTokenResponseReceivedImpl(TokenResponseReceivedContext context) { // 移除手动存储token的代码,依赖SaveTokens=true自动处理 return Task.CompletedTask; }
2. 调整登出事件逻辑,从外部登录上下文获取id_token
登出时通过AuthenticateAsync获取外部登录的认证结果,从中提取id_token:
private async Task OnRedirectToIdentityProviderForSignOutImpl(RedirectContext context) { var oidcMessage = context.ProtocolMessage; // 获取Okta外部登录的认证上下文 var externalAuthResult = await context.HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme); if (externalAuthResult.Succeeded) { // 从外部登录的Properties中提取id_token var idTokenClaim = externalAuthResult.Properties.GetTokenValue("id_token"); if (idTokenClaim != null) { oidcMessage.IdTokenHint = idTokenClaim; oidcMessage.PostLogoutRedirectUri = HomeUrl.AbsoluteUri; } } // ...其他登出逻辑 }
3. 确保登出流程触发外部身份提供商的登出
在发起登出的代码中,同时触发Identity Server和Okta的登出:
// 登出Identity Server本地会话 await HttpContext.SignOutAsync(IdentityServerConstants.SignoutScheme); // 触发Okta的登出流程 await HttpContext.SignOutAsync("oidc");
可选:在ProfileService中持久化id_token到本地用户声明
如果需要在本地用户身份中始终保留id_token,可以在Identity Server的IProfileService实现中添加该声明,确保登出时本地用户也能直接获取:
public class CustomProfileService : IProfileService { public async Task GetProfileDataAsync(ProfileDataRequestContext context) { // 从外部登录的认证信息中获取id_token(如果存在) var externalAuth = await context.HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme); if (externalAuth.Succeeded) { var idToken = externalAuth.Properties.GetTokenValue("id_token"); if (idToken != null) { context.IssuedClaims.Add(new Claim("id_token", idToken)); } } // ...其他Profile逻辑 } // ...实现IsActiveAsync方法 }
然后注册该ProfileService:
services.AddIdentityServer() .AddProfileService<CustomProfileService>() // ...其他Identity Server配置
内容的提问来源于stack exchange,提问作者David Klempfner

