GitHub OAuth API持续返回无效验证码问题排查求助
GitHub OAuth "bad verification code" 错误排查与修复方案
问题描述
学习API开发时,后端server.js中遇到GitHub OAuth问题:控制台持续返回"bad verification code(传入的验证码不正确或已过期)"错误。能正常从GitHub获取code,但用该验证码兑换access_token时触发错误。
此前在localhost:4000搭建的API搭配GitHub OAuth应用可正常工作:访问localhost:4000返回{"message":"Unauthorized"},通过localhost:4000/auth/github认证后可正常访问数据库数据。
后续搭建了运行在localhost:4001的前端应用,并创建新的GitHub OAuth应用,同时搭建运行在localhost:4002的后端服务,出现上述错误。已确认GitHub OAuth应用配置信息正确,调整/auth/github/callback路由逻辑后问题依旧。
代码片段
前端(app.js)
import React, { useState, useEffect } from 'react'; import axios from 'axios'; function App() { const [data, setData] = useState([]); const accessToken = process.env.SECRET_KEY useEffect(() => { if (accessToken) { fetchData(); } }, [accessToken]); async function fetchData() { try { // Use the obtained access token to fetch data from your API const response = await axios.get('http://localhost:4002/api/data', { headers: { Authorization: `Bearer ${accessToken}`, }, }); console.log("response") setData(response.data); } catch (error) { console.error('Error fetching data:', error); } } return ( <div> <h1>API Data</h1> <a href="http://localhost:4002/auth/github">Sign in</a> <ul> {data.map(item => ( <li key={item._id}> {`${item.firstname} ${item.lastname}, Age: ${item.age}, Color: ${item.color}`} </li> ))} </ul> </div> ); } export default App;
后端(server.js)
require('dotenv').config() const express = require('express') const axios = require('axios') const cors = require('cors') const passport = require('passport') const GitHubStrategy = require('passport-github').Strategy const http = require('http') const bodyParser = require('body-parser'); const app = express() // ... (other middleware and configurations) console.log('Server file is being executed') app.use(cors({ origin: 'http://localhost:4001', credentials: true, optionsSuccessStatus: 200, })) app.use(require('express-session')({ secret: process.env.SECRET_KEY, resave: false, saveUninitialized: true, })) app.use(bodyParser.urlencoded({ extended: false, limit: '10mb' })); app.use(bodyParser.json()); app.use(passport.initialize()) app.use(passport.session()) passport.use(new GitHubStrategy({ clientID: process.env.NEW_CLIENT_ID, clientSecret: process.env.NEW_CLIENT_SECRET, callbackURL: "http://localhost:4002/auth/github/callback", }, (accessToken, refreshToken, profile, done) => { return done(null, profile); })); passport.serializeUser((user, done) => { // Serialize user data into the session done(null, user); }); passport.deserializeUser((obj, done) => { // Deserialize user data from the session done(null, obj); }); app.get('/auth/github', passport.authenticate('github'), (req, res) => { console.log("just normal") // This callback function will be executed after successful authentication res.sendStatus(200); // You can send any response you want here }); app.get('/auth/github/callback', passport.authenticate('github', { failureRedirect: '/auth/github' }), async (req, res) => { try { const code = req.query.code const querystring = require('querystring') // Inside the callback route handler const data = querystring.stringify({ client_id: process.env.NEW_CLIENT_ID, client_secret: process.env.NEW_CLIENT_SECRET, code: code, grant_type: 'authorization_code', }) console.log("word:", data) const authOptions = { url: 'https://github.com/login/oauth/access_token', method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': 'Basic ' + Buffer.from(process.env.NEW_CLIENT_ID + ':' + process.env.NEW_CLIENT_SECRET).toString('base64'), }, data: data } console.log('test', authOptions) console.log('gone:', authOptions.data) console.log('over here:', code) console.log('Sending HTTP request:'); console.log('Method:', authOptions.method); console.log('URL:', authOptions.url); console.log('Headers:', authOptions.headers); console.log('Data:', authOptions.data); const response = await axios.post(authOptions.url, data, { headers: authOptions.headers, // data: authOptions.params }); console.log('Received HTTP response:'); console.log('Status:', response.status); console.log('Headers:', response.headers); console.log('Data:', response.data); console.log("mo:", response) console.log("response from Github:", response.data) if (response.data.access_token) { const accessToken = response.data.access_token console.log("Access Token:", accessToken) res.redirect('http://localhost:4001') } else { console.error("No access token received from Github.") res.status(500).json({ error: 'Internal Server Error here'}) } } catch (error) { console.error('Error exchanging code for access token:', error); res.status(500).json({ error: 'Internal Server Error 2' }); } } ) function ensureAuthenticated(req, res, next) { if (req.isAuthenticated()) { return next(); } res.status(401).json({ message: 'Unauthorized' }) } app.get('/protected-route', ensureAuthenticated, (req, res) => { // Route content for authenticated users }); const server = http.createServer(app) server.listen(4002, () => console.log('Server Started'));
问题根源分析
/auth/github/callback路由中存在重复操作:
- 调用
passport.authenticate('github')时,passport-github已经自动完成了code兑换access_token的流程,且code一旦被使用就会失效。 - 后续手动获取
req.query.code再次向GitHub请求兑换,此时code已被passport消耗,自然返回"bad verification code"错误。
另外,GitHubStrategy的回调函数中已经直接提供了accessToken参数,完全不需要手动重复兑换流程。
修复方案
核心修改点
删除/auth/github/callback路由中手动兑换code的所有逻辑,直接利用passport认证后得到的用户信息和accessToken。如果需要将accessToken传递给前端,可通过重定向参数(生产环境建议用session或安全方式传递,避免暴露token)或后端接口返回。
修改后的后端回调路由代码
app.get('/auth/github/callback', passport.authenticate('github', { failureRedirect: '/auth/github' }), (req, res) => { // passport认证成功后,req.user包含用户信息 console.log('Authenticated user:', req.user); // 重定向到前端并携带accessToken(示例,生产环境不建议直接在URL传token) res.redirect(`http://localhost:4001?access_token=${req.user.accessToken}`); } )
配套修改GitHubStrategy(用于保存accessToken)
passport.use(new GitHubStrategy({ clientID: process.env.NEW_CLIENT_ID, clientSecret: process.env.NEW_CLIENT_SECRET, callbackURL: "http://localhost:4002/auth/github/callback", }, (accessToken, refreshToken, profile, done) => { // 将accessToken挂载到profile对象,方便后续调用 profile.accessToken = accessToken; return done(null, profile); }));
额外优化建议
- 前端
app.js中accessToken = process.env.SECRET_KEY逻辑错误,SECRET_KEY是后端session密钥,并非用户的access_token。应从后端重定向的URL参数中获取,或通过接口请求获取。 - 生产环境中,禁止在URL中直接传递access_token,建议用HttpOnly Cookie存储,或后端提供接口让前端携带session凭证获取token。
内容的提问来源于stack exchange,提问作者Matt Ly
相关产品推荐
相关产品推荐

