GitHub OAuth 本地调试:回调地址、Session Cookie 和 302 循环怎么查

GitHub 登录失败时,表面上经常只是“又回到登录页”。真正原因可能是回调地址不一致、Session 没写进去、Cookie 没带回来,或者线上域名和本地地址混用了。

下面按 Express + passport-github2 的常见接法排查,不依赖某个特定项目。

1. 先把三个地址写清楚

本地开发通常有三个地址:

站点地址: http://127.0.0.1:3000
登录地址: http://127.0.0.1:3000/auth/github
回调地址: http://127.0.0.1:3000/auth/github/callback

GitHub OAuth App 里也必须填同一个回调地址。差一个 host、端口、路径、协议都不行。

本地统一用 127.0.0.1,不要一会儿 localhost 一会儿 127.0.0.1。Cookie 和 OAuth 回调排障时,少一个变量就少很多麻烦。

2. 环境变量不要混线上

.env 示例:

APP_BASE_URL=http://127.0.0.1:3000
GITHUB_CLIENT_ID=你的本地 OAuth App Client ID
GITHUB_CLIENT_SECRET=你的本地 OAuth App Client Secret
GITHUB_CALLBACK_URL=http://127.0.0.1:3000/auth/github/callback
SESSION_SECRET=本地随机长字符串
COOKIE_SECURE=false

检查进程实际读到什么:

node -e "require('dotenv').config(); console.log({base:process.env.APP_BASE_URL, callback:process.env.GITHUB_CALLBACK_URL, secure:process.env.COOKIE_SECURE})"

不要把 GITHUB_CLIENT_SECRET 打到日志里。上面只看地址和开关。

3. 登录地址应该返回 302 到 GitHub

启动服务后执行:

curl -I http://127.0.0.1:3000/auth/github

预期状态:

HTTP/1.1 302 Found
location: https://github.com/login/oauth/authorize?...

如果不是 302,查路由有没有挂上,不要急着改 GitHub 后台配置。

4. 回调地址错误时怎么看

GitHub 页面如果提示 callback URL 不匹配,先核对两处:

  • GitHub OAuth App 里的 Authorization callback URL。
  • 服务运行时传给 GitHubStrategy 的 callbackURL。

Express 里常见写法:

new GitHubStrategy({
  clientID: process.env.GITHUB_CLIENT_ID,
  clientSecret: process.env.GITHUB_CLIENT_SECRET,
  callbackURL: process.env.GITHUB_CALLBACK_URL
}, verifyUser)

如果代码里用 APP_BASE_URL + '/auth/github/callback' 拼,也要确认 APP_BASE_URL 没带结尾斜杠导致双斜杠。

5. 回调成功但又回登录页,先查 Session

登录成功后,服务通常会:

  1. 收到 GitHub 回调。
  2. 查找或创建用户。
  3. 把用户 id 写进 session。
  4. 给浏览器设置 session cookie。
  5. 跳转到首页或控制台。

浏览器最后又回 /login,说明第 3 或第 4 步可能没成。

用 curl 保存 cookie 看流程:

rm -f /tmp/codenest-cookie.txt
curl -i -c /tmp/codenest-cookie.txt http://127.0.0.1:3000/auth/github
cat /tmp/codenest-cookie.txt

OAuth 交互本身需要浏览器点授权,curl 不能完全替代。但 cookie 文件能帮你确认服务有没有设置基础 session cookie。

浏览器里看更直接:打开开发者工具,检查 Application / Cookies 里是否有类似:

connect.sid
codenest.sid

具体名字取决于项目配置。

如果本地是 HTTP:

http://127.0.0.1:3000

Session cookie 不能要求 Secure,否则浏览器不会在 HTTP 下保存它。

Express session 示例:

app.use(session({
  name: 'codenest.sid',
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,
    sameSite: 'lax',
    secure: process.env.COOKIE_SECURE === 'true'
  }
}));

本地 .env

COOKIE_SECURE=false

线上 HTTPS 再改成 true

7. 给 OAuth 流程加最小日志

不要记录 access token。只记录阶段和用户 id。

logger.info('github_oauth_callback_received', {
  path: req.path
});

logger.info('github_oauth_user_resolved', {
  user_id: user.id,
  github_id: profile.id
});

logger.info('github_oauth_login_finished', {
  user_id: req.user && req.user.id
});

如果失败,记录错误类型,不记录密钥:

logger.error('github_oauth_failed', {
  message: err.message,
  stack: err.stack
});

排查时看:

tail -n 80 data/logs/app.log
tail -n 80 data/logs/error.log

8. 302 循环的排查顺序

浏览器 Network 里点开跳转后的页面,看 Request Headers 是否带 Cookie。

没有 Cookie:查 Set-CookieSecure、域名、SameSite。

有 Cookie:查服务端 session store。

第二步:确认 session store 里有记录

如果用 SQLite 存 session,查表:

sqlite3 data/blog.db ".tables"
sqlite3 data/blog.db "select sid, expires from web_sessions order by expires desc limit 5;"

表名按项目实际改。能看到新 session,说明写入至少发生过。

第三步:确认反序列化用户成功

Passport 常见代码:

passport.deserializeUser((id, done) => {
  const user = db.prepare('SELECT * FROM users WHERE id = ?').get(id);
  done(null, user || false);
});

如果 session 里有用户 id,但数据库里找不到用户,也会表现成未登录。

9. 本地验收

用浏览器完成一次 GitHub 授权后,执行:

curl -I http://127.0.0.1:3000/login
curl -I http://127.0.0.1:3000/auth/github
sqlite3 data/blog.db "select id, username, github_id from users order by id desc limit 3;"
tail -n 50 data/logs/app.log
tail -n 50 data/logs/error.log

检查点:

  • /auth/github 返回 302 到 GitHub。
  • 授权后用户表出现或更新 GitHub 用户。
  • 浏览器里能看到 session cookie。
  • 登录后访问受保护页面不再跳回 /login
  • error.log 没有 OAuth callback、session 反序列化相关错误。