使用 Google 登录
Supabase Auth 支持 Web 平台的 Google 登录、原生应用程序(Android、macOS 和 iOS)以及 Chrome 扩展程序。
您可以通过两种方式使用 Google 登录
- 通过编写应用程序代码,用于 Web、原生应用程序或 Chrome 扩展程序
- 通过使用 Google 的预构建解决方案,例如 个性化登录按钮、一键登录 或 自动登录
先决条件#
要开始使用 Google 登录,您需要进行一些设置
- 准备一个 Google Cloud 项目。如果需要,请前往 Google Cloud Platform 创建一个新项目。
- 使用 Google Auth Platform 控制台 注册并设置您的应用程序的
- 受众 (Audience),通过配置允许哪些 Google 用户登录到您的应用程序。
- 数据访问 (Scopes) 定义您的应用程序可以使用用户 Google 数据和 API 的方式,例如访问个人资料信息或更多内容。
- 品牌 (Branding) 和 验证 (Verification) 在同意屏幕上显示徽标和名称,而不是 Supabase 项目 ID,从而提高用户留存率。品牌验证可能需要几个工作日。
设置所需的范围#
Supabase Auth 需要几个范围,授予访问用户个人资料数据的权限,您必须在 数据访问 (Scopes) 屏幕中配置这些范围
openid(手动添加).../auth/userinfo.email(默认添加).../auth/userinfo.profile(默认添加)
如果您添加了更多范围,尤其是那些在敏感或受限列表中的范围,您的应用程序可能需要进行验证,这可能需要很长时间。
设置同意屏幕品牌#
强烈建议您设置自定义域名,并选择性地使用 Google 验证您的品牌信息,因为这使得您的用户更容易识别网络钓鱼尝试。
Google 的同意屏幕会在用户登录时显示。您可以选择配置以下其中一项,以改善屏幕的外观,从而提高用户对您的信任度
- 通过在 Google Auth Platform 控制台的 品牌 部分进行配置,验证您的应用程序的品牌(徽标和名称)。品牌验证不是自动的,可能需要几个工作日。
- 设置 项目的自定义域名,以便向用户呈现与他们点击“使用 Google 登录”的网站的明确关系。
- 一个好的方法是使用
auth.example.com或api.example.com,如果您的应用程序托管在example.com上。 - 如果您没有进行此设置,用户将看到
<project-id>.supabase.co,这不会建立信任,并且可能使您的应用程序更容易受到成功的网络钓鱼攻击。
- 一个好的方法是使用
项目设置#
要支持使用 Google 登录,您需要配置 Supabase 项目的 Google 提供程序。
无论您使用应用程序代码还是 Google 的预构建解决方案来实现登录流程,您都需要在 Google Auth Platform 控制台的 客户端 部分获取客户端 ID 和客户端密钥,并配置您的项目
- 创建一个新的 OAuth 客户端 ID 并选择 Web 应用程序 作为应用程序类型。
- 在 授权的 JavaScript 来源 下,添加您的应用程序的 URL。这些也应配置为您的项目中的 站点 URL 或重定向配置。
- 如果您的应用程序托管在
https://example.com/app,请添加https://example.com。 - 在本地开发时添加
https://:<port>。请记住,当您的应用程序 投入生产 时删除此项。
- 如果您的应用程序托管在
- 在 授权的重定向 URI 下,添加您的 Supabase 项目的回调 URL。
- 从 仪表板上的 Google 提供程序页面 访问它。
- 对于本地开发,使用
http://127.0.0.1:54321/auth/v1/callback。
- 点击
创建并确保保存客户端 ID 和客户端密钥。- 将这些值添加到 仪表板上的 Google 提供程序页面。
本地开发#
要在本地开发中使用 Google 提供程序
- 添加一个新的环境变量
1SUPABASE_AUTH_EXTERNAL_GOOGLE_CLIENT_SECRET="<client-secret>"
- 在
supabase/config.toml中配置提供程序1[auth.external.google]2enabled = true3client_id = "<client-id>"4secret = "env(SUPABASE_AUTH_EXTERNAL_GOOGLE_CLIENT_SECRET)"5skip_nonce_check = false
如果您有多个客户端 ID,例如 Web、iOS 和 Android 各一个,请用逗号连接所有客户端 ID,但请确保 Web 的客户端 ID 位于列表的第一个位置。
使用管理 API#
使用 PATCH /v1/projects/{ref}/config/auth 管理 API 端点 以编程方式配置项目的 Auth 设置。要配置 Google 提供程序,请发送以下选项
1{2 "external_google_enabled": true,3 "external_google_client_id": "your-google-client-id",4 "external_google_secret": "your-google-client-secret"5}登录用户#
应用程序代码#
要使用您自己的应用程序代码进行登录按钮,请调用 signInWithOAuth 方法(或您语言的等效方法)。
请确保在以下代码中使用正确的 supabase 客户端。
如果您不使用服务器端渲染或基于 cookie 的身份验证,可以直接使用 @supabase/supabase-js 中的 createClient。 如果您正在使用服务器端渲染,请参阅 服务器端身份验证指南,了解有关创建 Supabase 客户端的说明。
1..({2 : 'google',3})对于隐式流程,您只需要执行这些操作。用户将被带到 Google 的同意屏幕,最后重定向到您的应用程序,并带有代表其会话的访问和刷新令牌对。
对于 PKCE 流程,例如在服务器端身份验证中,您需要额外的步骤来处理代码交换。 调用 signInWithOAuth 时,提供一个 redirectTo URL,该 URL 指向回调路由。 此重定向 URL 应该添加到您的 重定向允许列表。
在浏览器中,signInWithOAuth 会自动重定向到 OAuth 提供程序的身份验证端点,然后重定向到您的端点。
1await ..({2 ,3 : {4 : `http://example.com/auth/callback`,5 },6})在回调端点,处理代码交换以保存用户会话。
在 app/auth/callback/route.ts 中创建一个新文件并填充以下内容
app/auth/callback/route.ts
1import { NextResponse } from 'next/server'2// The client you created from the Server-Side Auth instructions3import { createClient } from '@/utils/supabase/server'45export async function GET(request: Request) {6 const { searchParams, origin } = new URL(request.url)7 const code = searchParams.get('code')8 // if "next" is in param, use it as the redirect URL9 let next = searchParams.get('next') ?? '/'10 if (!next.startsWith('/')) {11 // if "next" is not a relative URL, use the default12 next = '/'13 }1415 if (code) {16 const supabase = await createClient()17 const { error } = await supabase.auth.exchangeCodeForSession(code)18 if (!error) {19 const forwardedHost = request.headers.get('x-forwarded-host') // original origin before load balancer20 const isLocalEnv = process.env.NODE_ENV === 'development'21 if (isLocalEnv) {22 // we can be sure that there is no load balancer in between, so no need to watch for X-Forwarded-Host23 return NextResponse.redirect(`${origin}${next}`)24 } else if (forwardedHost) {25 return NextResponse.redirect(`https://${forwardedHost}${next}`)26 } else {27 return NextResponse.redirect(`${origin}${next}`)28 }29 }30 }3132 // return the user to an error page with instructions33 return NextResponse.redirect(`${origin}/auth/auth-code-error`)34}在代码交换成功后,用户的会话将保存到 cookie 中。
保存 Google 令牌#
您的应用程序保存的令牌是 Supabase Auth 令牌。您的应用程序可能还需要 Google OAuth 2.0 令牌才能代表用户访问 Google 服务。
在首次登录时,您可以从会话中提取 provider_token,并将其存储在安全的存储介质中。会话可在 signInWithOAuth(隐式流程)和 exchangeCodeForSession(PKCE 流程)返回的数据中获得。
Google 默认情况下不会发送刷新令牌,因此您需要将类似以下的参数传递给 signInWithOAuth(),才能提取 provider_refresh_token
1const { , } = await ..({2 : 'google',3 : {4 : {5 : 'offline',6 : 'consent',7 },8 },9})Google 预构建 #
大多数 Web 应用程序和网站可以使用 Google 的 个性化登录按钮、一键登录 或 自动登录,以获得最佳用户体验。
-
通过包含第三方脚本在您的应用程序中加载 Google 客户端库
1<script src="https://#/gsi/client" async></script> -
使用 HTML 代码生成器 自定义“使用 Google 登录”按钮的外观、感觉、功能和行为。
-
选择 切换到 JavaScript 回调 选项,并输入您的回调函数名称。此函数将在登录完成后接收
CredentialResponse。为了使您的应用程序与 Chrome 的第三方 cookie 淘汰计划兼容,请确保将
data-use_fedcm_for_prompt设置为true。您的最终 HTML 代码可能如下所示
1<div2id="g_id_onload"3data-client_id="<client ID>"4data-context="signin"5data-ux_mode="popup"6data-callback="handleSignInWithGoogle"7data-nonce=""8data-auto_select="true"9data-itp_support="true"10data-use_fedcm_for_prompt="true"11></div>1213<div14class="g_id_signin"15data-type="standard"16data-shape="pill"17data-theme="outline"18data-text="signin_with"19data-size="large"20data-logo_alignment="left"21></div> -
创建一个
handleSignInWithGoogle函数,该函数接收CredentialResponse并将包含的令牌传递给 Supabase。该函数需要可在全局范围内使用,以便 Google 的代码可以找到它。1async function handleSignInWithGoogle(response) {2const { data, error } = await supabase.auth.signInWithIdToken({3provider: 'google',4token: response.credential,5})6} -
(可选) 配置一个 nonce。建议使用 nonce 以获得额外的安全性,但它是可选的。nonce 应该每次都随机生成,并且必须在 HTML 代码的
data-nonce属性和回调函数的选项中提供。1async function handleSignInWithGoogle(response) {2const { data, error } = await supabase.auth.signInWithIdToken({3provider: 'google',4token: response.credential,5nonce: '<NONCE>',6})7}请注意,nonce 在这两个地方都应该是相同的,但由于 Supabase Auth 期望提供程序对其进行哈希处理(SHA-256,十六进制表示),因此您需要向 Google 提供哈希版本,并向
signInWithIdToken提供未哈希版本。您可以使用内置的
crypto库来获取这两个版本1// Adapted from https://mdn.org.cn/en-US/docs/Web/API/SubtleCrypto/digest#converting_a_digest_to_a_hex_string23const = (.(....(new (32))))4const = new ()5const = .()6..('SHA-256', ).(() => {7const = .(new ())8const = .(() => .(16).(2, '0')).('')9})1011// Use 'hashedNonce' when making the authentication request to Google12// Use 'nonce' when invoking the supabase.auth.signInWithIdToken() method
Next.js 的一键登录#
如果您正在将 Google One-Tap 与您的 Next.js 应用程序集成,可以参考下面的示例开始使用
1'use client'23import Script from 'next/script'4import { createClient } from '@/utils/supabase/client'5import type { accounts, CredentialResponse } from 'google-one-tap'6import { useRouter } from 'next/navigation'78declare const google: { accounts: accounts }910// generate nonce to use for google id token sign-in11const generateNonce = async (): Promise<string[]> => {12 const nonce = btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(32))))13 const encoder = new TextEncoder()14 const encodedNonce = encoder.encode(nonce)15 const hashBuffer = await crypto.subtle.digest('SHA-256', encodedNonce)16 const hashArray = Array.from(new Uint8Array(hashBuffer))17 const hashedNonce = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')1819 return [nonce, hashedNonce]20}2122const OneTapComponent = () => {23 const supabase = createClient()24 const router = useRouter()2526 const initializeGoogleOneTap = async () => {27 console.log('Initializing Google One Tap')28 const [nonce, hashedNonce] = await generateNonce()29 console.log('Nonce: ', nonce, hashedNonce)3031 // check if there's already an existing session before initializing the one-tap UI32 const { data, error } = await supabase.auth.getSession()33 if (error) {34 console.error('Error getting session', error)35 }36 if (data.session) {37 router.push('/')38 return39 }4041 /* global google */42 google.accounts.id.initialize({43 client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID,44 callback: async (response: CredentialResponse) => {45 try {46 // send id token returned in response.credential to supabase47 const { data, error } = await supabase.auth.signInWithIdToken({48 provider: 'google',49 token: response.credential,50 nonce,51 })5253 if (error) throw error54 console.log('Session data: ', data)55 console.log('Successfully logged in with Google One Tap')5657 // redirect to protected page58 router.push('/')59 } catch (error) {60 console.error('Error logging in with Google One Tap', error)61 }62 },63 nonce: hashedNonce,64 // with chrome's removal of third-party cookies, we need to use FedCM instead (https://developers.google.com/identity/gsi/web/guides/fedcm-migration)65 use_fedcm_for_prompt: true,66 })67 google.accounts.id.prompt() // Display the One Tap UI68 }6970 return <Script onReady={initializeGoogleOneTap} src="https://#/gsi/client" />71}7273export default OneTapComponent