Skip to content

GitHub OAuth 登录集成指南

GitHub OAuth 是最常用的第三方登录方式之一,特别适合开发者工具和技术社区。用户使用 GitHub 账号授权登录,无需单独注册,提升用户体验。

OAuth 流程概述#

1. 用户点击「GitHub 登录」
2. 跳转到 GitHub 授权页面
3. 用户同意授权
4. GitHub 回调你的应用,携带 code
5. 用 code 换取 access_token
6. 用 access_token 获取用户信息
7. 创建/登录用户

创建 GitHub OAuth App#

1. 注册应用#

  1. 访问 GitHub Developer Settings
  2. 点击「New OAuth App」
  3. 填写应用信息:
Application name: 你的应用名称
Homepage URL: https://your-app.com
Authorization callback URL: https://your-app.com/api/auth/callback/github

2. 获取凭证#

创建后获取:

Client ID: Ov23xxxxxxx
Client Secret: 点击 Generate a new client secret

注意:Client Secret 只显示一次,请妥善保存。

3. 环境变量#

.env.local
GITHUB_CLIENT_ID=Ov23xxxxxxx
GITHUB_CLIENT_SECRET=your_client_secret

手动实现 OAuth#

1. 发起授权请求#

app/api/auth/github/route.ts
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. 处理回调#

app/api/auth/callback/github/route.ts
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. 登录按钮#

components/LoginButton.tsx
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 集成。

安装#

Terminal window
npm install next-auth

配置#

app/api/auth/[...nextauth]/route.ts
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 }

环境变量#

Terminal window
GITHUB_CLIENT_ID=xxx
GITHUB_CLIENT_SECRET=xxx
NEXTAUTH_SECRET=your-secret-key # openssl rand -base64 32
NEXTAUTH_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#

app/providers.tsx
'use client'
import { SessionProvider } from 'next-auth/react'
export function Providers({ children }: { children: React.ReactNode }) {
return <SessionProvider>{children}</SessionProvider>
}
// app/layout.tsx
import { 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#

3. HTTPS#

生产环境必须使用 HTTPS,OAuth 回调地址也必须是 HTTPS。

常见问题#

回调地址不匹配#

确保 GitHub 配置的回调地址与实际一致,包括协议(http/https)和路径。

获取不到邮箱#

用户可能未公开邮箱。需要 user:email 权限并调用 /user/emails 接口。

Token 过期#

GitHub OAuth token 不会过期,但用户可能撤销授权。建议每次登录重新获取用户信息。

参考资料#