用谷歌登录
Supabase Auth 支持网页版用 Google 登录、原生应用(安卓、macOS 和 iOS)以及Chrome 扩展。
🌐 Supabase Auth supports Sign in with Google for the web, native applications (Android, macOS and iOS), and Chrome extensions.
你可以用谷歌账户登录有两种方式:
🌐 You can use Sign in with Google in two ways:
先决条件 #
🌐 Prerequisites
你需要做一些设置才能开始使用 Google 登录:
🌐 You need to do some setup to get started with Sign in with Google:
- 准备一个 Google Cloud 项目。去 Google Cloud Platform 并在必要时创建一个新项目。
- 使用 Google 身份验证平台控制台 来注册并设置你的应用的:
设置所需权限 #
🌐 Setup required scopes
Supabase Auth 需要一些权限范围来访问你终端用户的个人资料数据,你需要在 数据访问(Scopes) 页面中进行配置:
🌐 Supabase Auth needs a few scopes granting access to profile data of your end users, which you have to configure in the Data Access (Scopes) screen:
openid(手动添加).../auth/userinfo.email(默认添加).../auth/userinfo.profile(默认添加)
如果你添加更多权限,特别是那些敏感或受限制的权限,你的应用可能需要进行审核,这可能会花很长时间。
🌐 If you add more scopes, especially those on the sensitive or restricted list your application might be subject to verification which may take a long time.
设置同意屏幕品牌 #
🌐 Setup consent screen branding
强烈建议你设置一个自定义域名,并且可选地用 Google 验证你的品牌信息,这样可以让用户更容易识别钓鱼尝试。
🌐 It's strongly recommended you set up a custom domain and optionally verify your brand information with Google, as this makes phishing attempts easier to spot by your users.
当用户登录时,会显示 Google 的同意屏幕。你可以选择配置以下其中一项来改善屏幕的外观,从而提升用户对你的信任感:
🌐 Google's consent screen is shown to users when they sign in. Optionally configure one of the following to improve the appearance of the screen, increasing the perception of trust by your users:
- 通过在 Google Auth 平台控制台的 品牌 部分配置,验证你应用的品牌(徽标和名称)。品牌验证不是自动的,可能需要几个工作日。
- 为你的项目设置自定义域名,让用户在点击“用 Google 登录”时能清楚地看到与网站的关联。
- 一个不错的方法是使用
auth.example.com或api.example.com,如果你的应用托管在example.com上的话。 - 如果你不设置这个,用户会看到
<project-id>.supabase.co,这让人不太放心,还可能让你的应用更容易受到成功的钓鱼攻击。
- 一个不错的方法是使用
项目设置 #
🌐 Project setup
要支持使用 Google 登录,你需要为你的 Supabase 项目配置 Google 提供者。
🌐 To support Sign In with Google, you need to configure the Google provider for your Supabase project.
无论你是使用应用代码还是 Google 预构建的解决方案来实现登录流程,你都需要通过在 Google Auth 平台控制台的 Clients 部分获取客户端 ID 和客户端密钥来配置你的项目:
🌐 Regardless of whether you use application code or Google's pre-built solutions to implement the sign in flow, you need to configure your project by obtaining a Client ID and Client Secret in the Clients section of the Google Auth Platform console:
- 创建一个新的 OAuth 客户端 ID,并为应用类型选择 Web 应用。
- 在 Authorized JavaScript origins 下添加你的应用的 URL。这些也应该在你的项目中配置为 站点 URL 或重定向配置。
- 如果你的应用托管在
https://example.com/app上,添加https://example.com。 - 在本地开发时添加
http://localhost:<port>。记得在你的应用上线时将其移除。
- 如果你的应用托管在
- 在 Authorized redirect URIs 下添加你的 Supabase 项目的回调 URL。
- 从仪表板上的 Google 提供商页面 访问它。
- 在本地开发时,使用
http://127.0.0.1:54321/auth/v1/callback。
- 点击
Create并确保你保存了客户端 ID 和客户端密钥。- 将这些值添加到控制面板上的 Google 提供商页面。
本地开发 #
🌐 Local development
在本地开发中使用 Google 提供者:
🌐 To use the Google provider in local development:
-
添加一个新的环境变量:
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。
🌐 If you have multiple client IDs, such as one for Web, iOS and Android, concatenate all of the client IDs with a comma but make sure the web's client ID is first in the list.
使用管理 API #
🌐 Using the management API
使用 PATCH /v1/projects/{ref}/config/auth 管理 API 端点 来以编程方式配置项目的身份验证设置。要配置 Google 提供商,请发送以下选项:
🌐 Use the PATCH /v1/projects/{ref}/config/auth Management API endpoint to configure the project's Auth settings programmatically. For configuring the Google provider send these options:
1{2 "external_google_enabled": true,3 "external_google_client_id": "your-google-client-id",4 "external_google_secret": "your-google-client-secret"5}正在登录用户 #
🌐 Signing users in
应用代码 #
🌐 Application code
要在登录按钮上使用你自己的应用代码,请调用 signInWithOAuth 方法(或者你所用语言的等效方法)。
🌐 To use your own application code for the signin button, call the signInWithOAuth method (or the equivalent for your language).
确保你在以下代码中使用了正确的 supabase 客户端。
🌐 Make sure you're using the right supabase client in the following code.
如果你没有使用服务器端渲染或基于 Cookie 的认证,你可以直接从 @supabase/supabase-js 使用 createClient。如果你在使用服务器端渲染,请查看 服务器端认证指南 获取创建 Supabase 客户端的说明。
🌐 If you're not using Server-Side Rendering or cookie-based Auth, you can directly use the createClient from @supabase/supabase-js. If you're using Server-Side Rendering, see the Server-Side Auth guide for instructions on creating your Supabase client.
1import { createClient } from '@supabase/supabase-js'23const supabase = createClient('https://your-project-id.supabase.co', 'sb_publishable_...')45// ---cut---6supabase.auth.signInWithOAuth({7 provider: 'google',8})对于隐式流,这就是你需要做的全部。用户会被带到谷歌的同意界面,最后会被重定向回你的应用,并带上表示他们会话的访问令牌和刷新令牌。
🌐 For an implicit flow, that's all you need to do. The user will be taken to Google's consent screen, and finally redirected to your app with an access and refresh token pair representing their session.
以 PKCE 流程为例,比如在服务端认证中,你需要一个额外的步骤来处理代码交换。在调用 signInWithOAuth 时,提供一个指向回调路由的 redirectTo URL。这个重定向 URL 应该添加到你的 重定向允许列表 中。
🌐 For a PKCE flow, for example in Server-Side Auth, you need an extra step to handle the code exchange. When calling signInWithOAuth, provide a redirectTo URL which points to a callback route. This redirect URL should be added to your redirect allow list.
在浏览器中,signInWithOAuth 会自动重定向到 OAuth 提供商的认证端点,然后再重定向到你的端点。
🌐 In the browser, signInWithOAuth automatically redirects to the OAuth provider's authentication endpoint, which then redirects to your endpoint.
1import { createClient, type Provider } from '@supabase/supabase-js';2const supabase = createClient('https://your-project-id.supabase.co', 'sb_publishable_...')3const provider = 'provider' as Provider45// ---cut---6await supabase.auth.signInWithOAuth({7 provider,8 options: {9 redirectTo: `http://example.com/auth/callback`,10 },11})在回调端点,处理代码交换以保存用户会话。
🌐 At the callback endpoint, handle the code exchange to save the user session.
在 app/auth/callback/route.ts 创建一个新文件,并填入以下内容:
🌐 Create a new file at app/auth/callback/route.ts and populate with the following:
1import { NextResponse } from 'next/server'23// The client you created from the Server-Side Auth instructions4import { createClient } from '@/utils/supabase/server'56export async function GET(request: Request) {7 const { searchParams, origin } = new URL(request.url)8 const code = searchParams.get('code')9 // if "next" is in param, use it as the redirect URL10 let next = searchParams.get('next') ?? '/'11 if (!next.startsWith('/')) {12 // if "next" is not a relative URL, use the default13 next = '/'14 }1516 if (code) {17 const supabase = await createClient()18 const { error } = await supabase.auth.exchangeCodeForSession(code)19 if (!error) {20 const forwardedHost = request.headers.get('x-forwarded-host') // original origin before load balancer21 const isLocalEnv = process.env.NODE_ENV === 'development'22 if (isLocalEnv) {23 // we can be sure that there is no load balancer in between, so no need to watch for X-Forwarded-Host24 return NextResponse.redirect(`${origin}${next}`)25 } else if (forwardedHost) {26 return NextResponse.redirect(`https://${forwardedHost}${next}`)27 } else {28 return NextResponse.redirect(`${origin}${next}`)29 }30 }31 }3233 // return the user to an error page with instructions34 return NextResponse.redirect(`${origin}/auth/auth-code-error`)35}在成功交换代码后,用户的会话将会保存到 cookies 里。
🌐 After a successful code exchange, the user's session will be saved to cookies.
保存谷歌令牌 #
🌐 Saving Google tokens
你的应用保存的令牌是 Supabase Auth 令牌。你的应用可能还需要 Google OAuth 2.0 令牌,以代表用户访问 Google 服务。
🌐 The tokens saved by your application are the Supabase Auth tokens. Your app might additionally need the Google OAuth 2.0 tokens to access Google services on the user's behalf.
在首次登录时,你可以从会话中提取 provider_token 并将其存储在安全的存储介质中。会话可以在 signInWithOAuth(隐式流)和 exchangeCodeForSession(PKCE 流)返回的数据中获取。
🌐 On initial login, you can extract the provider_token from the session and store it in a secure storage medium. The session is available in the returned data from signInWithOAuth (implicit flow) and exchangeCodeForSession (PKCE flow).
谷歌默认不会发送刷新令牌,所以你需要像这样向 signInWithOAuth() 传递参数,才能获取 provider_refresh_token:
🌐 Google does not send out a refresh token by default, so you will need to pass parameters like these to signInWithOAuth() in order to extract the provider_refresh_token:
1import { createClient } from '@supabase/supabase-js'23const supabase = createClient('https://your-project-id.supabase.co', 'sb_publishable_...')45// ---cut---6const { data, error } = await supabase.auth.signInWithOAuth({7 provider: 'google',8 options: {9 queryParams: {10 access_type: 'offline',11 prompt: 'consent',12 },13 },14})谷歌预建 #
🌐 Google pre-built [#google-pre-built]
大多数网页应用和网站可以使用谷歌的个性化登录按钮、一键登录或自动登录来获得最佳用户体验。
🌐 Most web apps and websites can use Google's personalized sign-in buttons, One Tap or automatic sign-in for the best user experience.
-
在你的应用中通过引入第三方脚本来加载 Google 客户端库:
1<script src="https://accounts.google.com/gsi/client" async></script> -
使用 HTML 代码生成器 来自定义“使用谷歌登录”按钮的外观、感觉、功能和行为。
-
选择“切换到 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。这个函数需要在全局作用域中可用,以便谷歌的代码可以找到它。1async function handleSignInWithGoogle(response) {2const { data, error } = await supabase.auth.signInWithIdToken({3provider: 'google',4token: response.credential,5})6} -
(可选) 配置一个随机数。建议使用随机数以增加安全性,但这是可选的。每次都应该随机生成这个随机数,并且必须同时在 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://web.nodejs.cn/en-US/docs/Web/API/SubtleCrypto/digest#converting_a_digest_to_a_hex_string23const nonce = btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(32))))4const encoder = new TextEncoder()5const encodedNonce = encoder.encode(nonce)6crypto.subtle.digest('SHA-256', encodedNonce).then((hashBuffer) => {7const hashArray = Array.from(new Uint8Array(hashBuffer))8const hashedNonce = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')9})1011// Use 'hashedNonce' when making the authentication request to Google12// Use 'nonce' when invoking the supabase.auth.signInWithIdToken() method
一键使用 Next.js #
🌐 One-tap with Next.js
如果你正在将 Google 一键登录集成到你的 Next.js 应用中,可以参考下面的示例开始:
🌐 If you're integrating Google One-Tap with your Next.js application, you can refer to the example below to get started:
1'use client'23import type { accounts, CredentialResponse } from 'google-one-tap'4import { useRouter } from 'next/navigation'5import Script from 'next/script'67import { createClient } from '@/utils/supabase/client'89declare const google: { accounts: accounts }1011// generate nonce to use for google id token sign-in12const generateNonce = async (): Promise<string[]> => {13 const nonce = btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(32))))14 const encoder = new TextEncoder()15 const encodedNonce = encoder.encode(nonce)16 const hashBuffer = await crypto.subtle.digest('SHA-256', encodedNonce)17 const hashArray = Array.from(new Uint8Array(hashBuffer))18 const hashedNonce = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')1920 return [nonce, hashedNonce]21}2223const OneTapComponent = () => {24 const supabase = createClient()25 const router = useRouter()2627 const initializeGoogleOneTap = async () => {28 console.log('Initializing Google One Tap')29 const [nonce, hashedNonce] = await generateNonce()30 console.log('Nonce: ', nonce, hashedNonce)3132 // check if there's already an existing session before initializing the one-tap UI33 const {34 data: { claims },35 error,36 } = await supabase.auth.getClaims()37 if (error) {38 console.error('Error getting claims', error)39 }40 if (claims) {41 router.push('/')42 return43 }4445 /* global google */46 google.accounts.id.initialize({47 client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID,48 callback: async (response: CredentialResponse) => {49 try {50 // send id token returned in response.credential to supabase51 const { data, error } = await supabase.auth.signInWithIdToken({52 provider: 'google',53 token: response.credential,54 nonce,55 })5657 if (error) throw error58 console.log('Session data: ', data)59 console.log('Successfully logged in with Google One Tap')6061 // redirect to protected page62 router.push('/')63 } catch (error) {64 console.error('Error logging in with Google One Tap', error)65 }66 },67 nonce: hashedNonce,68 // with chrome's removal of third-party cookies, we need to use FedCM instead (https://developers.google.com/identity/gsi/web/guides/fedcm-migration)69 use_fedcm_for_prompt: true,70 })71 google.accounts.id.prompt() // Display the One Tap UI72 }7374 return <Script onReady={initializeGoogleOneTap} src="https://accounts.google.com/gsi/client" />75}7677export default OneTapComponent