Azure(Microsoft Graph)委托流中刷新令牌的处理及问题
项目背景
我正在开发一个需在用户Outlook日历中创建事件的项目,核心需求为将作业检查日期添加到相关用户的日历中,同时支持用户手动创建日历事件。
已实现内容
- 将用户重定向至Microsoft登录页面
- 用户登录后,获取该用户的access token
- 通过getTokenCache获取该用户的refresh token
代码实现
路由
router.get('/login', getAuthCodeUrl); router.get('/callback', handleCallback);
控制器
const { ConfidentialClientApplication } = require('@azure/msal-node'); // TEST APP const clientId = '945bf51b-xxxx-c5a83898b4b8'; const clientSecret = '9mV8Q~xxxx.zf9GqLGt95UUJ_bGdcp'; const msalConfig = { auth: { clientId: clientId, authority: `https://login.microsoftonline.com/common`, clientSecret: clientSecret, }, }; const redirectUri = 'http://localhost:3000/api/callback'; const scopes = [ 'User.Read', 'Calendars.ReadWrite', 'offline_access', 'openid', 'profile', ]; const cca = new ConfidentialClientApplication(msalConfig); const getAuthCodeUrl = async (req, res) => { const authCodeUrlParameters = { scopes, redirectUri, }; const authUrl = await cca.getAuthCodeUrl(authCodeUrlParameters); console.log('authUrl: ', authUrl); res.redirect(authUrl); }; const handleCallback = async (req, res) => { const tokenRequest = { scopes, code: req.query.code, redirectUri, accessType: 'offline', }; try { const authResult = await cca.acquireTokenByCode(tokenRequest); const accessToken = authResult.accessToken; const refreshToken = () => { const tokenCache = cca.getTokenCache().serialize(); const refreshTokenObject = JSON.parse(tokenCache).RefreshToken; const refreshToken = refreshTokenObject[Object.keys(refreshTokenObject)[0]].secret; return refreshToken; }; const tokens = { accessToken, refreshToken: refreshToken(), }; console.log('tokens: ', tokens); // Handle token result, store tokens, etc. res.send('Authentication successful. You can close this window.'); } catch (error) { console.error('Error obtaining access token:', error); res.status(500).send('Error obtaining access token'); } };
技术问题解答
1. 在refreshToken函数中未传入任何与用户相关的信息,MSAL如何精准获取该用户而非其他用户的刷新令牌?
你当前的实现仅在单用户测试场景下有效——因为全局的token cache里只有这一个用户的令牌。如果有多个用户登录,直接取缓存中第一个refresh token必然会出错。
MSAL的token cache是按用户(account对象)分区存储的,正确做法是从authResult中拿到当前用户的account对象,再通过该对象精准查询对应的refresh token,示例代码:
const refreshTokens = cca.getTokenCache().getRefreshTokens({ account: authResult.account }); const refreshToken = refreshTokens.length > 0 ? refreshTokens[0].secret : null;
2. 查阅资料得知“access token最长可刷新90天”,是否要求用户每90天必须重新登录一次?
是的。默认情况下,refresh token的有效期为90天,每次用它获取新access token时,通常会返回一个新的refresh token(滚动续期)。但如果连续90天未使用该refresh token,或者用户修改密码、撤销应用权限,refresh token就会失效,此时必须让用户重新登录才能获取新的令牌。
3. 能否无需用户重新登录即可获取新的refresh token?
可以。在使用refresh token获取access token的过程中,MSAL会自动返回新的refresh token(前提是当前refresh token未过期),你只需将新的refresh token替换旧的存储即可。但如果refresh token已过期或被撤销,就必须用户重新登录才能获取新的refresh token。
4. 如何判断access token是否过期?是否需要调用Profile或其他API来检查令牌的过期状态?
不需要调用API检查。access token是JWT格式,内部包含exp字段(过期时间戳),你可以解码JWT拿到该字段,与当前时间戳对比判断是否过期。
另外,MSAL提供了acquireTokenSilent方法,会自动检查缓存中的access token是否有效:如果未过期直接返回,过期则自动用refresh token获取新的access token,失败后才会触发用户重新登录。你也可以用@azure/msal-common中的decodeJwt工具函数解码令牌,快速获取过期时间。
内容的提问来源于stack exchange,提问作者Nikhil Mandaliya

