GitHub OAuth 是最常用的第三方登录方式之一,特别适合开发者工具和技术社区。用户使用 GitHub 账号授权登录,无需单独注册,提升用户体验。
OAuth 流程概述#
1. 用户点击「GitHub 登录」2. 跳转到 GitHub 授权页面3. 用户同意授权4. GitHub 回调你的应用,携带 code5. 用 code 换取 access_token6. 用 access_token 获取用户信息7. 创建/登录用户创建 GitHub OAuth App#
1. 注册应用#
- 访问 GitHub Developer Settings
- 点击「New OAuth App」
- 填写应用信息:
Application name: 你的应用名称Homepage URL: https://your-app.comAuthorization callback URL: https://your-app.com/api/auth/callback/github2. 获取凭证#
创建后获取:
Client ID: Ov23xxxxxxxClient Secret: 点击 Generate a new client secret注意:Client Secret 只显示一次,请妥善保存。
3. 环境变量#
GITHUB_CLIENT_ID=Ov23xxxxxxxGITHUB_CLIENT_SECRET=your_client_secret手动实现 OAuth#
1. 发起授权请求#
export async function GET() { const params = new URLSearchParams({ client_id: process.env.GITHUB_CLIENT_ID!, redirect_uri: `${process.env.NEXT_PUBLIC_URL}/api/auth/callback/github`, scope: 'read:user user:email', })
return Response.redirect(`https://github.com/login/oauth/authorize?${params}`)}2. 处理回调#
export async function GET(req: Request) { const { searchParams } = new URL(req.url) const code = searchParams.get('code')
if (!code) { return Response.redirect('/login?error=no_code') }
// 用 code 换取 access_token const tokenResponse = await fetch( 'https://github.com/login/oauth/access_token', { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify({ client_id: process.env.GITHUB_CLIENT_ID, client_secret: process.env.GITHUB_CLIENT_SECRET, code, }), } )
const { access_token } = await tokenResponse.json()
// 获取用户信息 const userResponse = await fetch('https://api.github.com/user', { headers: { Authorization: `Bearer ${access_token}`, }, })
const githubUser = await userResponse.json()
// 获取邮箱(可能需要额外请求) const emailResponse = await fetch('https://api.github.com/user/emails', { headers: { Authorization: `Bearer ${access_token}`, }, })
const emails = await emailResponse.json() const primaryEmail = emails.find((e: any) => e.primary)?.email
// 创建或更新用户 const user = await db.user.upsert({ where: { githubId: String(githubUser.id) }, update: { name: githubUser.name, avatar: githubUser.avatar_url, }, create: { githubId: String(githubUser.id), email: primaryEmail, name: githubUser.name || githubUser.login, avatar: githubUser.avatar_url, }, })
// 创建 session / JWT // ...
return Response.redirect('/dashboard')}3. 登录按钮#
export function GitHubLoginButton() { return ( <a href="/api/auth/github" className="flex items-center gap-2 px-4 py-2 bg-gray-900 text-white rounded-lg" > <GitHubIcon /> 使用 GitHub 登录 </a> )}使用 NextAuth.js#
NextAuth.js 大大简化了 OAuth 集成。
安装#
npm install next-auth配置#
import NextAuth from 'next-auth'import GitHubProvider from 'next-auth/providers/github'
const handler = NextAuth({ providers: [ GitHubProvider({ clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, }), ], callbacks: { async signIn({ user, account, profile }) { // 可以在这里创建/更新数据库用户 return true }, async session({ session, token }) { // 将用户 ID 添加到 session session.user.id = token.sub return session }, },})
export { handler as GET, handler as POST }环境变量#
GITHUB_CLIENT_ID=xxxGITHUB_CLIENT_SECRET=xxxNEXTAUTH_SECRET=your-secret-key # openssl rand -base64 32NEXTAUTH_URL=http://localhost:3000使用#
// 客户端'use client'
import { signIn, signOut, useSession } from 'next-auth/react'
export function AuthButton() { const { data: session } = useSession()
if (session) { return ( <div> <span>欢迎, {session.user?.name}</span> <button onClick={() => signOut()}>登出</button> </div> ) }
return ( <button onClick={() => signIn('github')}> 使用 GitHub 登录 </button> )}// 服务端import { getServerSession } from 'next-auth'
export default async function ProtectedPage() { const session = await getServerSession()
if (!session) { redirect('/login') }
return <div>欢迎, {session.user?.name}</div>}SessionProvider#
'use client'
import { SessionProvider } from 'next-auth/react'
export function Providers({ children }: { children: React.ReactNode }) { return <SessionProvider>{children}</SessionProvider>}
// app/layout.tsximport { Providers } from './providers'
export default function RootLayout({ children }) { return ( <html> <body> <Providers>{children}</Providers> </body> </html> )}使用 Supabase Auth#
如果使用 Supabase,可以直接使用其内置的 GitHub 登录。
配置#
Supabase Dashboard → Authentication → Providers → GitHub
填入 Client ID 和 Client Secret。
使用#
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(url, key)
// 登录await supabase.auth.signInWithOAuth({ provider: 'github', options: { redirectTo: `${window.location.origin}/auth/callback`, },})
// 获取用户const { data: { user },} = await supabase.auth.getUser()OAuth 权限范围#
| Scope | 权限 |
|---|---|
read:user | 读取用户公开信息 |
user:email | 读取用户邮箱 |
repo | 读写仓库(谨慎使用) |
public_repo | 读写公开仓库 |
gist | 读写 Gist |
只请求必要的权限,避免用户拒绝授权。
安全注意事项#
1. 验证 state 参数#
防止 CSRF 攻击:
// 发起授权时const state = crypto.randomUUID()cookies().set('oauth_state', state)
// 回调时验证const savedState = cookies().get('oauth_state')if (state !== savedState) { return Response.redirect('/login?error=invalid_state')}2. 保护 Client Secret#
- 永远不要在客户端暴露 Client Secret
- 使用环境变量存储
- 定期轮换
3. HTTPS#
生产环境必须使用 HTTPS,OAuth 回调地址也必须是 HTTPS。
常见问题#
回调地址不匹配#
确保 GitHub 配置的回调地址与实际一致,包括协议(http/https)和路径。
获取不到邮箱#
用户可能未公开邮箱。需要 user:email 权限并调用 /user/emails 接口。
Token 过期#
GitHub OAuth token 不会过期,但用户可能撤销授权。建议每次登录重新获取用户信息。