OAuth 提供商
规则
- 对公共客户端(SPA、移动应用)使用授权码流程(Authorization Code)配合 PKCE —— 绝不要使用隐式流程(Implicit flow)
- Web 应用的服务端代码交换 —— 客户端密钥永远不离开服务器
state 参数:生成随机 state,存储在会话/Cookie 中,在回调时验证 —— 防止 CSRF
- Google OAuth:作用域
openid email profile,使用 id_token 获取用户信息
- GitHub OAuth:作用域
read:user user:email,在 https://github.com/login/oauth/access_token 交换代码
- Discord OAuth:作用域
identify email,令牌端点为 https://discord.com/api/oauth2/token
- 令牌刷新:服务端存储
refresh_token,在 access_token 过期前刷新
- 会话管理:在 OAuth 回调成功时创建应用会话,不要将 OAuth 令牌存储在 Cookie 中
- 关联账户:允许用户通过
accounts 表将多个提供商关联到一个账户
模式
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const code = searchParams.get("code");
const state = searchParams.get("state");
if (state !== cookies().get("oauth_state")?.value) {
return new Response("无效的 state", { status: 400 });
}
const tokens = await exchangeCodeForTokens(code);
const userInfo = await fetchUserInfo(tokens.access_token);
const user = await upsertUser(userInfo);
await createSession(user.id);
return redirect("/dashboard");
}
避免
- 将客户端密钥存储在前端代码中 —— 始终在服务端交换令牌
- 跳过
state 参数 —— CSRF 攻击是真实存在的
- 将原始 OAuth 令牌存储在 Cookie 中 —— 使用服务端会话
- 在关联提供商时忘记处理“该邮箱的用户已存在”的情况