使用Microsoft.Identity.Client同时获取Access Token与Refresh Token
问题场景
我使用tenant id、client id、client secret、redirect uri及为用户生成的authorization code等参数,需要同时获取access token和refresh token,但当前API未返回refresh token,仅能获取access token和过期时间。
使用的代码如下:
ConfidentialClientApplicationOptions options = new ConfidentialClientApplicationOptions(); options.ClientId = clientId; options.TenantId = tenantId; options.ClientSecret = clientSecret; options.RedirectUri = redirectUri; ConfidentialClientApplicationBuilder builder = ConfidentialClientApplicationBuilder. CreateWithApplicationOptions(options); IConfidentialClientApplication app = builder.Build(); AcquireTokenByAuthorizationCodeParameterBuilder acquireTokenBuilder = app.AcquireTokenByAuthorizationCode(ServiceConstants.ALL_SCOPE_AUTHORIZATIONS.Split(' '), authorizationCode); AuthenticationResult result = await acquireTokenBuilder.ExecuteAsync(); string accessToken = result.AccessToken; // NO string refreshToken = result.RefreshToken
疑问:很多示例中AuthenticationResult里都有RefreshToken,但我的没有。请问这是什么原因?如何获取refresh token?另外,access token过期后,仅持有access token、tenant id、client id、密钥(或证书)及redirect uri,如何重新生成access token?
一、未返回Refresh Token的原因及解决方法
核心原因:未请求offline_access权限范围
Microsoft Identity Platform的规则是:只有在授权请求中明确包含offline_access范围,服务端才会返回Refresh Token。你的代码中使用的ServiceConstants.ALL_SCOPE_AUTHORIZATIONS大概率没有包含这个范围,导致无法获取Refresh Token。
解决步骤
修改权限范围,添加
offline_access
更新代码中的scopes参数,将offline_access加入请求范围:// 拆分原有范围并追加offline_access var scopes = ServiceConstants.ALL_SCOPE_AUTHORIZATIONS.Split(' ') .Append("offline_access") .ToArray(); AcquireTokenByAuthorizationCodeParameterBuilder acquireTokenBuilder = app.AcquireTokenByAuthorizationCode(scopes, authorizationCode); AuthenticationResult result = await acquireTokenBuilder.ExecuteAsync(); // 此时result.RefreshToken会正常返回 string refreshToken = result.RefreshToken;确认应用配置无限制
检查Azure AD中应用的配置:- 重定向URI与代码中
options.RedirectUri完全一致; - 应用类型为机密客户端(你使用
ConfidentialClientApplication符合要求,默认允许颁发Refresh Token)。
- 重定向URI与代码中
二、Access Token过期后的刷新方式
场景1:持有有效Refresh Token
直接使用Refresh Token请求新的Access Token,代码示例:
// 传入需要的权限范围和之前获取的Refresh Token var result = await app.AcquireTokenByRefreshToken(scopes, refreshToken) .ExecuteAsync(); // 获取新的Access Token和更新后的Refresh Token string newAccessToken = result.AccessToken; string newRefreshToken = result.RefreshToken; // 建议保存新的Refresh Token,旧的可能失效
场景2:无Refresh Token(仅持有过期的Access Token等信息)
这种情况无法直接刷新Token,因为过期的Access Token无法用来获取新的凭证。必须重新引导用户完成授权流程,获取新的Authorization Code,再通过Code换取新的Access Token和Refresh Token(务必确保这次请求包含offline_access范围)。
更简便的方式:利用MSAL令牌缓存自动刷新
MSAL SDK内置令牌缓存机制,只要缓存中存在有效Refresh Token,调用AcquireTokenSilent时会自动检测Token状态,过期则自动刷新:
try { // user为之前授权的用户对象,可从缓存中获取 var result = await app.AcquireTokenSilent(scopes, user) .ExecuteAsync(); string accessToken = result.AccessToken; } catch (MsalUiRequiredException) { // 缓存无有效Token,需重新引导用户授权 }
内容的提问来源于stack exchange,提问作者Adrien Ruffie

