如何在客户端脚本中检测Azure DevOps REST API的401错误?
问题描述
使用过期/无效的PAT调用Azure DevOps REST API时,浏览器DevTools控制台会显示:
GET https://dev.azure.com/contoso/_apis/connectionData net::ERR_FAILED 401 (Unauthorized)
但在JavaScript中使用fetch(包括then/catch、await+try/catch)或XMLHttpRequest时,仅能捕获到通用错误:
TypeError: Failed to fetch
同时浏览器会抛出CORS相关限制提示:
Access to fetch at 'https://dev.azure.com/contoso/_apis/connectionData' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
即使Network标签页明确显示401状态,设置mode: 'no-cors'只会得到不透明响应,依然无法获取401状态码。
可复现的示例代码:
<!DOCTYPE html> <html> <body> <script> (async function () { "use strict"; const API_ORGANIZATION="contoso" const expiredPat = 'ts44tplifj4hbbzeitcz5dg5az4q3mpqcxqmrxo4zwlgy3scobzq' const pat = expiredPat // Fetch data from Azure DevOps async function ado(api) { const url = `https://dev.azure.com/${API_ORGANIZATION}/_apis/${api}` console.log(`url: ${url}`) const response = await fetch(url, { method: 'GET', cache: 'no-cache', mode: 'cors', headers: { Authorization: 'Basic ' + btoa(':' + pat), Accept: 'application/json', } }) return await response.json() } // get the connectionData from Azure DevOps async function getConnectionData() { return await ado('connectionData') } function topText(text) { var p = document.createElement('p'); p.innerHTML = text document.body.prepend(p) return p } // show the connection Data at the top of the window async function showConnectionData() { try { const result = await getConnectionData(); topText(`Azure DevOps access authenticated as: ${result.authenticatedUser.providerDisplayName}`) } catch(err) { const p = topText(`${err} - See console`) p.style.color = "red"; p.style.fontWeight = "999" } } async function tryFetch() { try { await showConnectionData() } catch(err) { console.log(err); } } document.addEventListener("DOMContentLoaded", function(event) { tryFetch() }); })(); </script> </body> </html>
问题根源
这是浏览器CORS安全策略的限制:当请求返回401未授权时,Azure DevOps服务器未返回Access-Control-Allow-Origin响应头,浏览器会直接拦截完整响应,不让JavaScript代码获取具体状态码,仅抛出通用的TypeError。
可行解决方案
1. 使用后端代理(推荐)
客户端不直接调用Azure DevOps API,通过自有后端服务转发请求:
- 后端调用Azure DevOps API不受浏览器CORS限制,可正常获取401状态码
- 后端将状态码和响应内容返回给客户端,客户端即可精准判断PAT是否无效
示例后端逻辑(Node.js):
const express = require('express'); const fetch = require('node-fetch'); const app = express(); app.get('/api/ado/:endpoint', async (req, res) => { const pat = req.headers['x-pat']; const endpoint = req.params.endpoint; const url = `https://dev.azure.com/contoso/_apis/${endpoint}`; try { const response = await fetch(url, { headers: { 'Authorization': `Basic ${Buffer.from(`:${pat}`).toString('base64')}`, 'Accept': 'application/json' } }); res.status(response.status); res.json(await response.json()); } catch (err) { res.status(500).json({ error: err.message }); } }); app.listen(3000, () => console.log('Proxy server running on port 3000'));
客户端修改为调用代理接口:
async function ado(api) { const url = `http://localhost:3000/api/ado/${api}`; const response = await fetch(url, { method: 'GET', headers: { 'X-PAT': pat, 'Accept': 'application/json', } }); if (!response.ok) { if (response.status === 401) { throw new Error('无效或过期的PAT'); } throw new Error(`请求失败: ${response.status}`); } return await response.json(); }
2. 配置Azure DevOps CORS白名单(需管理员权限)
若你拥有Azure DevOps组织管理员权限,可将客户端域名添加到组织级CORS白名单:
- 登录Azure DevOps组织,进入
组织设置->安全性->CORS - 添加客户端域名(如
http://localhost:8080) - 保存后,Azure DevOps会在响应中返回
Access-Control-Allow-Origin头,客户端即可直接获取401状态码
注意:此方法不适用于本地文件(origin: null)场景,必须部署到有明确域名的服务器上。
3. 纯客户端环境的妥协方案
若无后端代理权限,也无法修改Azure DevOps CORS设置,纯客户端脚本无法精准捕获401状态。此时可通过捕获TypeError: Failed to fetch,结合用户输入PAT的场景,提示用户检查PAT是否有效或过期,但无法100%确认是401导致的错误。
内容的提问来源于stack exchange,提问作者zumalifeguard

