You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Azure AD B2C使用UseOpenIdConnectAuthentication无法获取Refresh Token

问题:.NET 4.6.2中Azure AD B2C授权码流无法获取Refresh Token及相关疑问

问题现象

  • 在Authentication.Challenge步骤设置AuthenticationProperties.AllowRefresh = true,但AuthenticationTicket.Properties.AllowRefresh始终为false
  • ProtocolMessage.AccessToken包含有效访问令牌,但ProtocolMessage.RefreshToken始终为null
  • 上述情况在SecurityTokenValidated和AuthorizationCodeReceived回调中均存在

核心原因

  1. 缺少必要Scope:Azure AD B2C返回Refresh Token必须请求offline_access范围,当前配置仅设置了openid,不满足条件
  2. 中间件默认行为限制:OWIN的OpenID Connect中间件在混合流(code id_token)模式下,不会自动处理Refresh Token的获取,需手动发起令牌交换请求
  3. Properties属性未被正确继承:Authentication.Challenge设置的AllowRefresh不会自动同步到AuthenticationTicket.Properties,需手动修正

解决方案

1. 更新OpenID Connect配置,添加offline_access Scope

修改Scope配置,包含获取Refresh Token必需的范围:

Scope = $"{OpenIdConnectScopes.OpenId} offline_access",

2. 手动修正AllowRefresh属性值

在SecurityTokenValidated回调中,强制设置AllowRefresh为true:

private Task OnOrgUserSecurityTokenValidated(SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
    // 手动同步AllowRefresh设置
    context.AuthenticationTicket.Properties.AllowRefresh = true;
    // 原有业务逻辑...
    return Task.CompletedTask;
}

3. 主动发起授权码交换获取Refresh Token

在AuthorizationCodeReceived回调中,用授权码向Azure AD B2C的Token端点发起请求,获取Refresh Token并存储:

private async Task OnOrgAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
    var tokenEndpoint = new Uri(new Uri(context.Options.Authority), "/oauth2/v2.0/token");
    
    var tokenRequest = new Dictionary<string, string>
    {
        ["grant_type"] = "authorization_code",
        ["client_id"] = context.Options.ClientId,
        ["client_secret"] = "YOUR_CLIENT_SECRET", // 生产环境建议用密钥托管服务存储,禁止硬编码
        ["code"] = context.Code,
        ["redirect_uri"] = context.Options.RedirectUri,
        ["scope"] = $"{OpenIdConnectScopes.OpenId} offline_access"
    };

    using (var client = new HttpClient())
    {
        var response = await client.PostAsync(tokenEndpoint, new FormUrlEncodedContent(tokenRequest));
        response.EnsureSuccessStatusCode();
        
        var tokenResponse = await response.Content.ReadAsAsync<JObject>();
        
        // 提取并存储Refresh Token到AuthenticationTicket
        var refreshToken = tokenResponse["refresh_token"]?.ToString();
        if (!string.IsNullOrEmpty(refreshToken))
        {
            context.AuthenticationTicket.Properties.Dictionary["refresh_token"] = refreshToken;
            // 存储Refresh Token过期时间(若返回)
            var refreshTokenExpiresIn = tokenResponse["refresh_token_expires_in"]?.ToObject<int>();
            if (refreshTokenExpiresIn.HasValue)
            {
                context.AuthenticationTicket.Properties.ExpiresUtc = DateTime.UtcNow.AddSeconds(refreshTokenExpiresIn.Value);
            }
        }
    }
}

关于ROPC刷新方式的疑问

不可行。ROPC流的刷新令牌仅适用于ROPC流的令牌刷新,授权码流获取的Refresh Token必须使用grant_type=refresh_token的方式发起刷新请求,需完全重构原有刷新逻辑:

  • 当ID Token即将过期时,使用存储的Refresh Token向B2C Token端点请求新令牌,参数包含grant_type=refresh_token、client_id、client_secret、refresh_token及所需Scope
  • 若Refresh Token过期(返回invalid_grant错误),需清除用户认证Cookie并引导重新登录

后续优化建议

  • 安全存储Client Secret:使用Azure Key Vault等安全托管服务,避免硬编码敏感信息
  • 完善过期处理逻辑:捕获令牌刷新时的invalid_grant错误,及时触发用户重新登录流程
  • 持久化Refresh Token:可将Refresh Token关联用户ID存储到数据库,避免用户清除Cookie后需重新登录

内容的提问来源于stack exchange,提问作者Kiran Ramaswamy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 10:17:46