使用Google登录
Supabase Auth 支持通过 Google 登录网页应用、原生 Android 应用和 Chrome 扩展程序。
先决条件
- 一个 Google Cloud 项目。前往 Google Cloud Platform 并在必要时创建新项目。
配置
要支持 Google 登录功能,您需要为 Supabase 项目配置 Google 提供商。
对于网页应用,您可以通过两种方式设置登录按钮:
- 使用自己的应用程序代码创建按钮
- 使用Google预构建的登录或One Tap流程
应用程序代码配置
要使用您自己的应用程序代码:
-
在 Google Cloud 控制台中,转到同意屏幕配置页面。同意屏幕是用户同意登录您的应用时显示的界面。
-
在授权域名下,添加您的 Supabase 项目域名,格式为
<PROJECT_ID>.supabase.co
。 -
配置以下非敏感权限范围:
.../auth/userinfo.email
...auth/userinfo.profile
openid
-
转到API 凭证页面。
-
点击
创建凭证
并选择OAuth 客户端 ID
。 -
对于应用程序类型,选择
Web 应用程序
。 -
在授权的 JavaScript 来源下,添加您的网站 URL。
-
在授权的重定向 URL中,输入来自 Supabase 仪表盘 的回调 URL。展开 Google 认证提供程序部分即可显示该 URL。
重定向 URL 对用户可见。您可以通过配置自定义域名来自定义它。
-
完成凭证配置后,系统会显示您的客户端 ID 和密钥。将这些信息添加到 Supabase 仪表盘的 Google 认证提供程序部分。
在本地开发环境中,您可以将客户端 ID 和密钥添加到
config.toml
文件中。
Google 预构建配置
要使用 Google 预构建的登录按钮:
- 在 Google Cloud 控制台中,前往同意屏幕配置页面。同意屏幕是用户同意登录您的应用时显示的界面。
- 根据喜好配置该屏幕,确保添加了指向您应用隐私政策和服务条款的链接。
- 前往API 凭据页面。
- 点击
创建凭据
并选择OAuth 客户端 ID
。 - 在应用类型中选择
网络应用
。 - 在授权的 JavaScript 来源和授权的重定向 URI部分,添加您的网站 URL。这是登录按钮出现的网站 URL,而非您的 Supabase 项目域名。如果在本地测试,请确保在授权的 JavaScript 来源部分也设置了
http://localhost
。这在集成 Google One-Tap 时很重要,确保您可以在本地使用它。 - 完成凭据配置后,将显示您的客户端 ID。将其添加到 Supabase 仪表板的 Google 认证提供商部分 的客户端 ID字段中。保留 OAuth 客户端 ID 和密钥为空。使用 Google 预构建方法时不需要这些。
用户登录
应用代码
要使用自己的应用代码实现登录按钮,可以调用 signInWithOAuth
方法(或对应语言的等效方法)。
请确保在以下代码中使用正确的 supabase
客户端。
如果您未使用服务器端渲染或基于 cookie 的身份验证,可以直接使用 @supabase/supabase-js
中的 createClient
。如果您正在使用服务器端渲染,请参阅 服务器端身份验证指南,了解创建 Supabase 客户端的说明。
123..({ : 'google',})
对于隐式流程,这就是您需要做的全部操作。用户将被引导至Google的授权页面,最终携带代表其会话的访问令牌和刷新令牌对重定向回您的应用。
以 PKCE 流程为例,比如在服务器端身份验证中,您需要额外的步骤来处理代码交换。调用 signInWithOAuth
时,提供一个指向回调路由的 redirectTo
URL。此重定向 URL 应添加到您的重定向允许列表中。
在浏览器中,signInWithOAuth
会自动重定向到 OAuth 提供商的身份验证端点,然后再重定向到您的端点。
123456await ..({ , : { : `http://example.com/auth/callback`, },})
在回调端点,处理代码交换以保存用户会话。
在 app/auth/callback/route.ts
创建一个新文件,并填充以下内容:
app/auth/callback/route.ts
12345678910111213141516171819202122232425262728293031323334import { NextResponse } from 'next/server'// 您根据服务器端身份验证说明创建的客户端import { createClient } from '@/utils/supabase/server'export async function GET(request: Request) { const { searchParams, origin } = new URL(request.url) const code = searchParams.get('code') // 如果参数中有 "next",则将其用作重定向 URL let next = searchParams.get('next') ?? '/' if (!next.startsWith('/')) { // 如果 "next" 不是相对 URL,则使用默认值 next = '/' } if (code) { const supabase = await createClient() const { error } = await supabase.auth.exchangeCodeForSession(code) if (!error) { const forwardedHost = request.headers.get('x-forwarded-host') // 负载均衡器之前的原始来源 const isLocalEnv = process.env.NODE_ENV === 'development' if (isLocalEnv) { // 我们可以确定中间没有负载均衡器,因此无需关注 X-Forwarded-Host return NextResponse.redirect(`${origin}${next}`) } else if (forwardedHost) { return NextResponse.redirect(`https://${forwardedHost}${next}`) } else { return NextResponse.redirect(`${origin}${next}`) } } } // 将用户重定向到带有说明的错误页面 return NextResponse.redirect(`${origin}/auth/auth-code-error`)}
成功完成代码交换后,用户会话将被保存至cookies。
保存Google令牌
您的应用保存的是Supabase Auth令牌。如果应用需要代表用户访问Google服务,则可能还需要Google OAuth 2.0令牌。
在初始登录时,您可以从会话中提取 provider_token
并存储到安全介质中。该会话可通过 signInWithOAuth
(隐式流程)和 exchangeCodeForSession
(PKCE流程)的返回数据获取。
Google默认不发送刷新令牌,因此您需要向 signInWithOAuth()
传递如下参数才能获取 provider_refresh_token
:
123456789const { , } = await ..({ : 'google', : { : { : 'offline', : 'consent', }, },})
Google 预构建方案
大多数Web应用和网站都可以使用Google的个性化登录按钮、One Tap或自动登录来获得最佳用户体验。
-
在应用中加载Google客户端库,引入第三方脚本:
1<script src="https://accounts.google.com/gsi/client" async></script> -
使用HTML代码生成器自定义Google登录按钮的外观、功能和行为。
-
选择_Swap to JavaScript callback_选项,并输入回调函数名称。该函数将在登录完成时接收
CredentialResponse
。为确保应用兼容Chrome的第三方Cookie淘汰计划,请务必将
data-use_fedcm_for_prompt
设置为true
。最终HTML代码可能如下所示:
123456789101112131415161718192021<div id="g_id_onload" data-client_id="<client ID>" data-context="signin" data-ux_mode="popup" data-callback="handleSignInWithGoogle" data-nonce="" data-auto_select="true" data-itp_support="true" data-use_fedcm_for_prompt="true"></div><div class="g_id_signin" data-type="standard" data-shape="pill" data-theme="outline" data-text="signin_with" data-size="large" data-logo_alignment="left"></div> -
创建
handleSignInWithGoogle
函数,接收CredentialResponse
并将包含的令牌传递给Supabase。该函数需在全局作用域中可用,以便Google代码能找到它。123456async function handleSignInWithGoogle(response) { const { data, error } = await supabase.auth.signInWithIdToken({ provider: 'google', token: response.credential, })} -
(可选) 配置nonce。建议使用nonce增强安全性,但这是可选的。nonce应每次随机生成,并需同时在HTML代码的
data-nonce
属性和回调函数选项中提供。1234567async function handleSignInWithGoogle(response) { const { data, error } = await supabase.auth.signInWithIdToken({ provider: 'google', token: response.credential, nonce: '<NONCE>', })}注意两处的nonce应相同,但由于Supabase Auth要求提供商对其进行哈希处理(SHA-256,十六进制表示),您需要向Google提供哈希版本,而向
signInWithIdToken
提供未哈希版本。可以使用内置的
crypto
库获取两个版本:123456789101112// 改编自 https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest#converting_a_digest_to_a_hex_stringconst = (.(....(new (32))))const = new ()const = .()..('SHA-256', ).(() => { const = .(new ()) const = .(() => .(16).(2, '0')).('')})// 向Google发起认证请求时使用'hashedNonce'// 调用supabase.auth.signInWithIdToken()方法时使用'nonce'
使用 Next.js 实现一键登录
如果您正在将 Google 一键登录集成到 Next.js 应用程序中,可以参考以下示例开始:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283'use client'import Script from 'next/script'import { createClient } from '@/utils/supabase/client'import { CredentialResponse } from 'google-one-tap'import { useRouter } from 'next/navigation'import { useEffect } from 'react'const OneTapComponent = () => { const supabase = createClient() const router = useRouter() // 生成用于 Google ID 令牌登录的 nonce const generateNonce = async (): Promise<string[]> => { const nonce = btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(32)))) const encoder = new TextEncoder() const encodedNonce = encoder.encode(nonce) const hashBuffer = await crypto.subtle.digest('SHA-256', encodedNonce) const hashArray = Array.from(new Uint8Array(hashBuffer)) const hashedNonce = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('') return [nonce, hashedNonce] } useEffect(() => { const initializeGoogleOneTap = () => { console.log('正在初始化 Google 一键登录') window.addEventListener('load', async () => { const [nonce, hashedNonce] = await generateNonce() console.log('Nonce: ', nonce, hashedNonce) // 在初始化一键登录 UI 前检查是否已有会话 const { data, error } = await supabase.auth.getSession() if (error) { console.error('获取会话时出错', error) } if (data.session) { router.push('/') return } /* global google */ google.accounts.id.initialize({ client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID, callback: async (response: CredentialResponse) => { try { // 将响应中的 ID 令牌发送到 Supabase const { data, error } = await supabase.auth.signInWithIdToken({ provider: 'google', token: response.credential, nonce, }) if (error) throw error console.log('会话数据: ', data) console.log('已成功通过 Google 一键登录') // 重定向到受保护页面 router.push('/') } catch (error) { console.error('Google 一键登录时出错', error) } }, nonce: hashedNonce, // 随着 Chrome 移除第三方 Cookie,我们需要改用 FedCM (https://developers.google.com/identity/gsi/web/guides/fedcm-migration) use_fedcm_for_prompt: true, }) google.accounts.id.prompt() // 显示一键登录 UI }) } initializeGoogleOneTap() return () => window.removeEventListener('load', initializeGoogleOneTap) }, []) return ( <> <Script src="https://accounts.google.com/gsi/client" /> <div id="oneTap" className="fixed top-0 right-0 z-[100]" /> </> )}export default OneTapComponent
Google 授权界面
默认情况下,Google 授权界面会显示回调 URL 的根域名,即 Google 发送认证响应的目标地址。对于 Supabase Auth 来说,这就是您 Supabase 项目的域名 (https://<your-project-ref>.supabase.co)
。
如果这不是您想要的,您可以为 Supabase 项目使用自定义域名。在应用程序中创建 Supabase 客户端并初始化认证流程时,可以使用该域名作为项目域名。这样它就会显示在 Google 授权界面上。如果您希望在授权界面上显示应用名称和徽标,必须将应用提交给 Google 进行验证。