Google OAuth 是全球使用最广泛的第三方登录方式。用户使用 Google 账号授权登录,可以快速获取用户基本信息和邮箱,提升注册转化率。
OAuth 流程概述#
1. 用户点击「Google 登录」2. 跳转到 Google 授权页面3. 用户选择账号并同意授权4. Google 回调你的应用,携带 code5. 用 code 换取 access_token 和 id_token6. 解析 id_token 或调用 API 获取用户信息7. 创建/登录用户创建 Google OAuth 应用#
1. 创建项目#
- 访问 Google Cloud Console
- 创建新项目或选择已有项目
- 启用 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.profileopenid3. 创建 OAuth 凭证#
APIs & Services → Credentials → Create Credentials → OAuth client ID
Application type: Web applicationName: 你的应用名称Authorized JavaScript origins: https://your-app.comAuthorized redirect URIs: https://your-app.com/api/auth/callback/google4. 获取凭证#
Client ID: xxx.apps.googleusercontent.comClient Secret: GOCSPX-xxx5. 环境变量#
GOOGLE_CLIENT_ID=xxx.apps.googleusercontent.comGOOGLE_CLIENT_SECRET=GOCSPX-xxx手动实现 OAuth#
1. 发起授权请求#
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. 处理回调#
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. 登录按钮#
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#
配置#
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 }环境变量#
GOOGLE_CLIENT_ID=xxx.apps.googleusercontent.comGOOGLE_CLIENT_SECRET=GOCSPX-xxxNEXTAUTH_SECRET=your-secret-keyNEXTAUTH_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>初始化#
'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#
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 | 权限 |
|---|---|
openid | OpenID 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 服务在中国大陆无法直接访问。解决方案:
- 服务端部署在海外
- 用户使用 VPN
- 提供其他登录方式