MERN栈使用Passport实现Google认证时的CORS错误求助
问题描述
在MERN应用中使用Passport实现Google登录认证时,直接在新标签页打开错误提示中的认证链接可正常完成流程,但通过Axios发起请求时触发CORS错误:从http://localhost:3000访问重定向后的Google认证地址时,因目标资源缺少Access-Control-Allow-Origin头被CORS策略拦截。
错误信息
Access to XMLHttpRequest at 'https://accounts.google.com/o/oauth2/v2/auth?response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Fauth%2Fgoogle%2Fcallback&scope=profile%20email&client_id=1000813228585-k44nrfc0plt3kmupqu4rqcj0qcohhm35.apps.googleusercontent.com' (redirected from 'http://localhost:8000/auth/google') from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
当前配置代码
CORS配置
app.use(cors({ origin: ["http://localhost:3000"," https://accounts.google.com"], // 注意:第二个origin前有多余空格 methods: "GET,POST,PUT,DELETE", credentials: true, }));
Passport路由
CLIENT_URL=http://localhost:3000
router.get("/google", passport.authenticate("google", ["profile"])); router.get("/google/callback", passport.authenticate("google", { successRedirect: process.env.CLIENT_URL, failureRedirect: "/login/failed", })); router.get("/login/failed", (req, res) => { res.status(401).json({ error: true, message: "Log in failure", }); });
客户端Axios请求
REACT_APP_API_URL=http://localhost:8000
export const googleLoginReq = async () => { try { let res = await axios.get(`${REACT_APP_API_UR}/auth/google`, { withCredentials: true }); // 注意:REACT_APP_API_UR拼写错误 return res.data; } catch (error) { console.log(error); } };
请求头信息
General
Request URL: https://accounts.google.com/o/oauth2/v2/auth?response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Fauth%2Fgoogle%2Fcallback&scope=profile%20email&client_id=1000813228585-k44nrfc0plt3kmupqu4rqcj0qcohhm35.apps.googleusercontent.com Request Method: GET Status Code: 200 Referrer Policy: strict-origin-when-cross-origin
响应头
alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43" cache-control: no-cache, no-store, max-age=0, must-revalidate content-encoding: gzip content-security-policy: require-trusted-types-for 'script';report-uri /cspreport content-security-policy: script-src 'report-sample' 'nonce-TOmywtPu0Qo8iQCEZhMnTw' 'unsafe-inline' 'unsafe-eval';object-src 'none';base-uri 'self';report-uri /cspreport content-type: text/html; charset=utf-8 cross-origin-opener-policy-report-only: same-origin; report-to="coop_gse_qebhlk" date: Wed, 25 Jan 2023 10:01:03 GMT expires: Mon, 01 Jan 1990 00:00:00 GMT p3p: CP="This is not a P3P policy! See http://www.google.com/support/accounts/bin/answer.py?hl=en&answer=151657 for more info." pragma: no-cache report-to: {"group":"coop_gse_qebhlk","max_age":2592000,"endpoints":[{"url":"https://csp.withgoogle.com/csp/report-to/gse_qebhlk"}]} server: GSE set-cookie: __Host-GAPS=1:WBh43ekzNFz-U7PJu6wrySL4P0fJVw:-8nse8F4eB1X5yrO;Path=/;Expires=Fri, 24-Jan-2025 10:01:02 GMT;Secure;HttpOnly;Priority=HIGH strict-transport-security: max-age=31536000; includeSubDomains x-content-type-options: nosniff x-frame-options: DENY x-xss-protection: 1; mode=block
请求头
:authority: accounts.google.com :method: GET :path: /o/oauth2/v2/auth?response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Fauth%2Fgoogle%2Fcallback&scope=profile%20email&client_id=1000813228585-k44nrfc0plt3kmupqu4rqcj0qcohhm35.apps.googleusercontent.com :scheme: https accept: application/json, text/plain, */* accept-encoding: gzip, deflate, br accept-language: en-IN,en-US;q=0.9,en;q=0.8 cookie: __Secure-3PAPISID=0fADWvGybtHw6h4E/AuZfyvMRCsCGQG4m9; __Secure-3PSID=TAgcETHlEPmlnVokgxJP67RdvdDCwLo6zS5s8W_yA_tnNn79ks2clkRgFmsvP0yZRult_A.; __Host-3PLSID=o.chat-dl.google.com|o.chat.google.com|o.console.cloud.google.com|o.drive.google.com|o.mail.google.com|o.meet.google.com|o.myaccount.google.com|s.IN|s.youtube:TAgcET_mZKiKd0P8KBaXK00vnWmuYQPOt8sRxUc75wk7hbKaEbtuam5S-oCMNPtkvpnT_w.; NID=511=ui5rQ6D8-KrHAUOn491ZvzQP7VdaQ-r94ktEYHfqHSWwWXK9E29AWZYBZMZRE8g9Zesepsp6c6lp-VjKU1dUWus4ICU9FkArFbf7bw5E6wQFcb9mOasQA0yV1qPgVErv49i1GLrgpHc373TwTqgC1Oah1nme4oaN2VBBN-r5YyAXun1eL4P-u3Iw7W6SV7ExIqnkunp4bQoG9ThcP5DjlkyDy14sK_YuV5EZwGIm9P6a18SsDcHDE2quUps7_LdjcE_CRL_OcB8v53R-Y9msRdxrBNGhY23yvgFbVkANBh5wKElpTvfTG7vxrDxZW_8Ol3LcM_wmKdD1ePuPHQrh4v1wQ29i16Vd0Z7obEEfP2hdig; LSOLH=AH+1Ng3ry+24BKCDnw1k/j8gBn5vXNTEUY4wwBo52xPp9smoYZt/jdHVLmQLmcStBp6zxduzEbOG; 1P_JAR=2023-01-25-10; __Secure-3PSIDCC=AIKkIs3zFB8Qc0Oo3-AzH4hzyJkZFsjp0ZEMsrezMMUBxtGeayEzfxjxwSaygz9LHj3OTAhrEdFL origin: null referer: http://localhost:3000/ sec-ch-ua: "Not_A Brand";v="99", "Google Chrome";v="109", "Chromium";v="109" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" sec-fetch-dest: empty sec-fetch-mode: cors sec-fetch-site: cross-site user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36
Google API凭证配置

解决方案
1. 核心修正:改用浏览器直接跳转而非AJAX请求
Google OAuth2授权码流程要求浏览器直接跳转到Google登录页,AJAX请求会触发跨域检查,但Google的认证接口不会返回允许localhost:3000的CORS头,必然被拦截。
替换客户端登录逻辑:
export const googleLoginReq = () => { // 修正拼写错误:REACT_APP_API_UR → REACT_APP_API_URL window.location.href = `${process.env.REACT_APP_API_URL}/auth/google`; };
或在组件中直接使用a标签:
<a href={`${process.env.REACT_APP_API_URL}/auth/google`}>Google登录</a>
2. 修正CORS配置错误
移除origin数组中https://accounts.google.com前的多余空格,且无需将Google域名加入CORS允许列表(Google不会使用你的CORS配置):
app.use(cors({ origin: ["http://localhost:3000"], methods: "GET,POST,PUT,DELETE", credentials: true, }));
3. 修正客户端代码拼写错误
将REACT_APP_API_UR改为REACT_APP_API_URL,避免跳转地址错误。
4. 确认Google凭证配置
确保Google API凭证中的授权重定向URI与后端Passport配置的redirect_uri完全一致(即http://localhost:8000/auth/google/callback),从截图看当前配置正确,无需修改。
内容的提问来源于stack exchange,提问作者Deepanshu Gupta

