Skip to content

Google OAuth 登录集成指南

Google OAuth 是全球使用最广泛的第三方登录方式。用户使用 Google 账号授权登录,可以快速获取用户基本信息和邮箱,提升注册转化率。

OAuth 流程概述#

1. 用户点击「Google 登录」
2. 跳转到 Google 授权页面
3. 用户选择账号并同意授权
4. Google 回调你的应用,携带 code
5. 用 code 换取 access_token 和 id_token
6. 解析 id_token 或调用 API 获取用户信息
7. 创建/登录用户

创建 Google OAuth 应用#

1. 创建项目#

  1. 访问 Google Cloud Console
  2. 创建新项目或选择已有项目
  3. 启用 Google+ API(或 People API)

2. 配置 OAuth 同意屏幕#

APIs & Services → OAuth consent screen

User Type: External(外部用户)
App name: 你的应用名称
User support email: 你的邮箱
Developer contact: 你的邮箱

添加 Scopes:

.../auth/userinfo.email
.../auth/userinfo.profile
openid

3. 创建 OAuth 凭证#

APIs & Services → Credentials → Create Credentials → OAuth client ID

Application type: Web application
Name: 你的应用名称
Authorized JavaScript origins: https://your-app.com
Authorized redirect URIs: https://your-app.com/api/auth/callback/google

4. 获取凭证#

Client ID: xxx.apps.googleusercontent.com
Client Secret: GOCSPX-xxx

5. 环境变量#

.env.local
GOOGLE_CLIENT_ID=xxx.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-xxx

手动实现 OAuth#

1. 发起授权请求#

app/api/auth/google/route.ts
export async function GET() {
const params = new URLSearchParams({
client_id: process.env.GOOGLE_CLIENT_ID!,
redirect_uri: `${process.env.NEXT_PUBLIC_URL}/api/auth/callback/google`,
response_type: 'code',
scope: 'openid email profile',
access_type: 'offline',
prompt: 'consent',
})
return Response.redirect(
`https://accounts.google.com/o/oauth2/v2/auth?${params}`
)
}

2. 处理回调#

app/api/auth/callback/google/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 换取 token
const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
client_id: process.env.GOOGLE_CLIENT_ID!,
client_secret: process.env.GOOGLE_CLIENT_SECRET!,
code,
grant_type: 'authorization_code',
redirect_uri: `${process.env.NEXT_PUBLIC_URL}/api/auth/callback/google`,
}),
})
const tokens = await tokenResponse.json()
const { access_token, id_token } = tokens
// 方式1:解析 id_token(推荐)
const payload = JSON.parse(
Buffer.from(id_token.split('.')[1], 'base64').toString()
)
const googleUser = {
id: payload.sub,
email: payload.email,
name: payload.name,
avatar: payload.picture,
emailVerified: payload.email_verified,
}
// 方式2:调用 API 获取用户信息
// const userResponse = await fetch(
// 'https://www.googleapis.com/oauth2/v2/userinfo',
// { headers: { Authorization: `Bearer ${access_token}` } }
// )
// const googleUser = await userResponse.json()
// 创建或更新用户
const user = await db.user.upsert({
where: { googleId: googleUser.id },
update: {
name: googleUser.name,
avatar: googleUser.avatar,
},
create: {
googleId: googleUser.id,
email: googleUser.email,
name: googleUser.name,
avatar: googleUser.avatar,
emailVerified: googleUser.emailVerified ? new Date() : null,
},
})
// 创建 session / JWT
// ...
return Response.redirect('/dashboard')
}

3. 登录按钮#

components/LoginButton.tsx
export function GoogleLoginButton() {
return (
<a
href="/api/auth/google"
className="flex items-center gap-2 px-4 py-2 border rounded-lg hover:bg-gray-50"
>
<GoogleIcon />
使用 Google 登录
</a>
)
}

使用 NextAuth.js#

配置#

app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth'
import GoogleProvider from 'next-auth/providers/google'
const handler = NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
],
callbacks: {
async signIn({ user, account, profile }) {
// 自定义登录逻辑
return true
},
async session({ session, token }) {
session.user.id = token.sub
return session
},
},
})
export { handler as GET, handler as POST }

环境变量#

Terminal window
GOOGLE_CLIENT_ID=xxx.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-xxx
NEXTAUTH_SECRET=your-secret-key
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 className="flex items-center gap-4">
<img
src={session.user?.image || ''}
alt=""
className="w-8 h-8 rounded-full"
/>
<span>{session.user?.name}</span>
<button onClick={() => signOut()}>登出</button>
</div>
)
}
return (
<button onClick={() => signIn('google')}>
使用 Google 登录
</button>
)
}

使用 Supabase Auth#

配置#

Supabase Dashboard → Authentication → Providers → Google

填入 Client ID 和 Client Secret。

使用#

import { createClient } from '@supabase/supabase-js'
const supabase = createClient(url, key)
// 登录
await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: `${window.location.origin}/auth/callback`,
queryParams: {
access_type: 'offline',
prompt: 'consent',
},
},
})
// 获取用户
const {
data: { user },
} = await supabase.auth.getUser()

Google One Tap#

Google One Tap 提供更流畅的登录体验,用户无需跳转页面。

添加脚本#

<script src="https://accounts.google.com/gsi/client" async defer></script>

初始化#

components/GoogleOneTap.tsx
'use client'
import { useEffect } from 'react'
export function GoogleOneTap() {
useEffect(() => {
window.google?.accounts.id.initialize({
client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID!,
callback: handleCredentialResponse,
})
window.google?.accounts.id.prompt()
}, [])
async function handleCredentialResponse(response: { credential: string }) {
// response.credential 是 id_token
await fetch('/api/auth/google/one-tap', {
method: 'POST',
body: JSON.stringify({ credential: response.credential }),
})
window.location.href = '/dashboard'
}
return null
}

验证 Token#

app/api/auth/google/one-tap/route.ts
import { OAuth2Client } from 'google-auth-library'
const client = new OAuth2Client(process.env.GOOGLE_CLIENT_ID)
export async function POST(req: Request) {
const { credential } = await req.json()
const ticket = await client.verifyIdToken({
idToken: credential,
audience: process.env.GOOGLE_CLIENT_ID,
})
const payload = ticket.getPayload()!
// 创建用户和 session
// ...
return Response.json({ success: true })
}

OAuth 权限范围#

Scope权限
openidOpenID Connect
email用户邮箱
profile用户基本信息
https://www.googleapis.com/auth/calendar日历
https://www.googleapis.com/auth/drive云盘

只请求必要的权限。敏感权限需要 Google 审核。

安全注意事项#

1. 验证 id_token#

生产环境应使用 Google 库验证 token:

import { OAuth2Client } from 'google-auth-library'
const client = new OAuth2Client(process.env.GOOGLE_CLIENT_ID)
const ticket = await client.verifyIdToken({
idToken: token,
audience: process.env.GOOGLE_CLIENT_ID,
})
const payload = ticket.getPayload()

2. 验证 state 参数#

防止 CSRF 攻击:

const state = crypto.randomUUID()
cookies().set('oauth_state', state)
// 回调时验证
if (state !== cookies().get('oauth_state')) {
throw new Error('Invalid state')
}

3. 检查邮箱验证状态#

if (!payload.email_verified) {
throw new Error('Email not verified')
}

常见问题#

回调地址不匹配#

确保 Google Cloud Console 配置的回调地址与代码中一致,包括协议和路径。

应用未验证警告#

开发阶段会显示「此应用未经 Google 验证」,正式上线需提交验证。

无法获取 refresh_token#

需要在授权时添加:

access_type: 'offline',
prompt: 'consent',

中国大陆访问问题#

Google 服务在中国大陆无法直接访问。解决方案:

参考资料#