如何用原生JavaScript无第三方库验证OAuth 2.0令牌
纯原生JavaScript实现Azure AD OAuth 2.0令牌校验方案
完全不依赖MSAL或第三方库的前提下,有三种成熟可行的实现路径,你可以根据自己的部署场景(服务端/前端SPA)和性能要求选择:
方案1:调用官方令牌自省端点(服务端场景首选,实现成本最低)
这个方案完全不需要你处理签名验证、公钥轮换这类底层逻辑,直接调用微软身份平台标准的RFC 7662自省端点完成校验:
- 端点格式:
https://login.microsoftonline.com/{你的Azure AD租户ID}/oauth2/v2.0/introspect - 请求规则:以
application/x-www-form-urlencoded格式POST提交参数,需要带你应用注册时拿到的client_id、client_secret,以及待校验的令牌 - 原生JS实现示例:
async function validateTokenViaIntrospect(accessToken, tenantId, clientId, clientSecret) { const reqParams = new URLSearchParams(); reqParams.append('token', accessToken); reqParams.append('client_id', clientId); reqParams.append('client_secret', clientSecret); const res = await fetch(`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/introspect`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: reqParams }); const data = await res.json(); // 返回结果中active字段为true即代表令牌有效,同时会返回令牌的过期时间、作用域、所属用户等元信息 return data.active === true; }
- 适用限制:仅适合服务端场景,
client_secret必须存储在服务端,绝对不能写到前端代码里,否则会泄露应用凭证。
方案2:本地离线校验(前端SPA场景首选,无额外网络开销)
Azure AD签发的OAuth 2.0令牌(id_token、access_token)都是标准JWT格式,你可以用浏览器/Node.js原生支持的Web Crypto API完成本地校验,不需要发请求到微软服务:
- 校验流程分三步:
- 基础声明校验:先解码JWT的Payload部分,校验令牌是否在有效期内(对比
exp过期时间戳、nbf生效时间戳和当前时间),校验aud受众字段是否为你自己的应用Client ID,校验iss签发者字段是否匹配你的租户地址,先过滤掉明显无效的令牌 - 拉取签名公钥:从你租户的公开元数据端点拉取当前在用的签名公钥列表,公钥轮换频率很低,本地缓存24小时即可,不需要每次校验都拉取
- 签名校验:用Web Crypto API导入对应公钥,校验JWT的签名是否合法,这一步是核心,跳过的话会存在伪造令牌绕过的风险
- 基础声明校验:先解码JWT的Payload部分,校验令牌是否在有效期内(对比
- 无依赖JWT解析+基础校验代码示例:
// 解析JWT结构,不需要第三方库 function parseJwt(token) { const [headerSeg, payloadSeg] = token.split('.'); const base64UrlDecode = (seg) => { const base64 = seg.replace(/-/g, '+').replace(/_/g, '/'); const pad = base64.length % 4 ? '='.repeat(4 - (base64.length % 4)) : ''; return JSON.parse(atob(base64 + pad)); }; return { header: base64UrlDecode(headerSeg), payload: base64UrlDecode(payloadSeg) }; } // 基础声明校验 function checkTokenClaims(payload, clientId, tenantId) { const currentTs = Math.floor(Date.now() / 1000); if (payload.exp < currentTs) return false; // 令牌已过期 if (payload.nbf > currentTs) return false; // 令牌尚未生效 if (payload.aud !== clientId) return false; // 令牌不是发给当前应用的 if (!payload.iss.includes(tenantId)) return false; // 令牌不是对应租户签发的 return true; }
- 注意:签名校验部分直接用原生
crypto.subtle.verify接口实现RS256签名验证即可,不需要引入任何第三方加密库。
方案3:受保护接口反向校验(轻量场景适用)
如果你本身就需要用令牌调用微软Graph或自有受保护接口,可以直接用待校验的令牌请求轻量接口,通过响应状态判断令牌有效性:
- 最常用的检测端点是微软Graph的
/me接口,只需要令牌带基础的User.Read权限即可调用,不需要额外配置 - 实现示例:
async function validateTokenViaApiCall(accessToken) { try { const res = await fetch('https://graph.microsoft.com/v1.0/me', { headers: { 'Authorization': `Bearer ${accessToken}` } }); return res.ok; // 状态码200代表令牌有效,401代表令牌无效/过期 } catch (err) { return false; } }
- 适用限制:每次校验会产生一次网络请求,受接口限流规则约束,适合校验频率不高的场景。
避坑提醒:不要只解码JWT的Payload做时间、声明校验就判定令牌有效,必须完成签名校验或者调用官方端点确认,否则攻击者可以构造任意内容的假令牌绕过校验逻辑。
内容的提问来源于stack exchange,提问作者Maulik Dave
相关产品推荐
相关产品推荐

