Microsoft Graph API通过code获取access token时出现400 Bad Request错误求助
问题:微软OAuth2.0授权码换取Token时POST请求返回400 Bad Request
按照微软OAuth2.0授权流程,第一步授权请求能正常跳转并获取code,但第二步用code换token的POST请求返回400错误(net::ERR_ABORTED 400 (Bad Request))。代码如下:
const getMicrosoftToken = async () => { if (window.location.href === 'http://localhost:8080/') { location.assign(`http://login.microsoftonline.com/${process.env.VUE_APP_TENANT_ID}/oauth2/v2.0/authorize?client_id=${process.env.VUE_APP_CLIENT_ID}&response_type=code&redirect_uri=${process.env.VUE_APP_REDIRECT_URI}&response_mode=query&scope=offline_access%20chat.readwrite&state=12345`); } else { const code = new URLSearchParams(window.location.search).get('code'); const result = await fetch(`http://login.microsoftonline.com/${process.env.VUE_APP_TENANT_ID}/oauth2/v2.0/token?client_id=${process.env.VUE_APP_CLIENT_ID}&scope=chatmessage.send%20chat.readwrite&code=${code}&redirect_uri=${process.env.VUE_APP_REDIRECT_URI}&grant_type=authorization_code`, { method: 'POST', mode: 'no-cors' }); console.log(result); } };
流程说明:先重定向到授权页面,获取code后跳转回本地,再发起POST请求换token。请问该如何解决?
解决方案
以下是核心问题的修正点:
1. 修正请求参数传递方式
微软OAuth2.0的token端点要求POST请求参数以application/x-www-form-urlencoded格式放在请求体中,而非拼接在URL上。同时禁止使用mode: 'no-cors'——该模式会让浏览器屏蔽所有响应内容和错误信息,无法排查具体问题。
2. 保证scope参数完全一致
授权请求时的scope是offline_access chat.readwrite,换token时的scope必须与授权时完全一致(或为授权scope的子集),否则会触发400错误。
3. 添加client_secret(针对Web应用)
如果你的应用是Web类型的机密客户端,换token时必须携带client_secret参数(在Azure门户中获取),同样放在请求体里。
修改后的完整代码
const getMicrosoftToken = async () => { const baseUrl = `http://login.microsoftonline.com/${process.env.VUE_APP_TENANT_ID}/oauth2/v2.0`; // 初始页面跳转授权 if (window.location.href === 'http://localhost:8080/') { const authUrl = `${baseUrl}/authorize?client_id=${process.env.VUE_APP_CLIENT_ID}&response_type=code&redirect_uri=${encodeURIComponent(process.env.VUE_APP_REDIRECT_URI)}&response_mode=query&scope=offline_access%20chat.readwrite&state=12345`; location.assign(authUrl); } else { // 获取URL中的code const code = new URLSearchParams(window.location.search).get('code'); if (!code) return; // 构造请求体参数 const formData = new URLSearchParams(); formData.append('client_id', process.env.VUE_APP_CLIENT_ID); formData.append('scope', 'offline_access chat.readwrite'); // 和授权时的scope保持一致 formData.append('code', code); formData.append('redirect_uri', process.env.VUE_APP_REDIRECT_URI); formData.append('grant_type', 'authorization_code'); // Web应用需添加client_secret formData.append('client_secret', process.env.VUE_APP_CLIENT_SECRET); try { const result = await fetch(`${baseUrl}/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: formData }); if (!result.ok) { // 打印具体错误信息便于排查 const errorData = await result.json(); console.error('Token请求错误:', errorData); return; } const tokenData = await result.json(); console.log('获取Token成功:', tokenData); // 这里可处理token,比如存入localStorage } catch (err) { console.error('请求异常:', err); } } };
内容的提问来源于stack exchange,提问作者Yosi Linchevsky
相关产品推荐
相关产品推荐

