用 Apple 登录
Supabase Auth 支持在网页以及 iOS、macOS、watchOS 或 tvOS 的原生应用中使用 使用 Apple 登录。
🌐 Supabase Auth supports using Sign in with Apple on the web and in native apps for iOS, macOS, watchOS or tvOS.
概览 #
🌐 Overview
要支持使用 Apple 登录,你需要在 Supabase 仪表板中为你的项目配置 Apple 提供者。
🌐 To support Sign in with Apple, you need to configure the Apple provider in the Supabase dashboard for your project.
根据你想要构建的应用,有三种常见方法可以使用“使用 Apple 登录”:
🌐 There are three general ways to use Sign in with Apple, depending on the application you're trying to build:
- 在网页或基于网页的应用中登录
- 使用 Supabase Auth 发起的 OAuth 流程,通过 用 Apple REST API 登录 实现。
- 直接在浏览器中使用 通过 Apple JS 登录,通常适合网站。
- 在 iOS、macOS、watchOS 或 tvOS 应用中本地登录,使用 Apple 的身份验证服务
在某些情况下,你可以在基于网页的原生应用中使用 OAuth 流程,比如使用 React Native、Expo 或其他类似框架。不过,最好在这些平台上使用原生的 Apple 登录功能。
🌐 In some cases you're able to use the OAuth flow within web-based native apps such as with React Native, Expo or other similar frameworks. It is best practice to use native Sign in with Apple capabilities on those platforms instead.
在使用 Expo 开发时,你可以通过 Expo Go 应用测试“使用 Apple 登录”,在其他情况下,你需要获取一个 Apple 开发者 账户来启用该功能。
🌐 When developing with Expo, you can test Sign in with Apple via the Expo Go app, in all other cases you will need to obtain an Apple Developer account to enable the capability.
需要更换密钥
如果你在使用 OAuth 流程(网页、Flutter 网页、Kotlin 非 iOS 平台),Apple 要求你每六个月用签名密钥(.p8 文件)生成一个新的密钥。这是一个关键的维护任务,如果遗漏会导致认证失败。
🌐 If you're using the OAuth flow (web, Flutter web, Kotlin non-iOS platforms), Apple requires you to generate a new secret key every 6 months using the signing key (.p8 file). This is a critical maintenance task that will cause authentication failures if missed.
- 设置一个每6个月重复的日历提醒来更换你的密钥
- 把
.p8文件安全存好——每次轮换都需要用到它 - 如果你丢了
.p8文件或者它被泄露了,马上在 Apple 开发者控制台撤销它,然后创建一个新的 - 如果可能的话,考虑自动化这个过程,以防服务中断
这个要求只适用于你在配置 OAuth 设置(服务 ID、签名密钥等)的时候。仅限原生的实现不需要轮换密钥。
🌐 This requirement only applies if you're configuring OAuth settings (Services ID, signing key, etc.). Native-only implementations don't require secret key rotation.
苹果在身份令牌中不提供全名
苹果的身份令牌在其声明中不包含用户的全名。这意味着当用户使用苹果登录时,Supabase Auth 服务器无法自动填充用户的名称元数据。
🌐 Apple's identity token does not include the user's full name in its claims. This means the Supabase Auth server cannot automatically populate the user's name metadata when users sign in with Apple.
- 苹果只会在用户第一次登录尝试时提供用户的全名(也就是用户最初授权你的应用时)
- 之后的所有登录都会在全名字段返回
null - 必须从苹果原生认证响应中获取全名,并使用
updateUser方法手动保存
推荐方法:
在成功使用 Apple 登录后,检查认证响应中是否有完整名称,如果有的话,使用 updateUser 方法将其保存到用户的元数据中:
1// Example: Handling full name after successful sign in2if (credential.fullName) {3 // Full name is only provided on first sign-in4 await supabase.auth.updateUser({5 data: {6 full_name: `${credential.fullName.givenName} ${credential.fullName.familyName}`,7 given_name: credential.fullName.givenName,8 family_name: credential.fullName.familyName,9 },10 })11}如果用户撤销了你应用的访问权限然后又重新授权,苹果会像首次登录一样再次提供完整名称。
🌐 If a user revokes your app's access and then re-authorizes it, Apple will provide the full name again as if it were a first sign-in.
下面的针对特定平台的例子展示了如何在每个 SDK 中实现这一模式。
🌐 The platform-specific examples below demonstrate how to implement this pattern for each SDK.
在网页上使用 OAuth 流程#
使用苹果的 OAuth 流程登录是为网页或基于浏览器的登录方式设计的。它可以用于基于网页的应用以及网站,不过有些用户可以通过直接使用 Apple JS 登录获得好处。
在背后,Supabase Auth 使用 Apple 提供的 REST APIs。
确保你在以下代码中使用了正确的 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.
要开始登录,你可以使用 Supabase JavaScript 库里的 signInWithOAuth() 方法:
1import { createClient } from '@supabase/supabase-js'2const supabase = createClient('https://your-project-id.supabase.co', 'sb_publishable_...')34// ---cut---5supabase.auth.signInWithOAuth({6 provider: 'apple',7})这个调用会带用户进入苹果的同意屏幕。流程结束后,用户的个人信息会与 Supabase Auth 进行交换和验证,然后再重定向回你的网页应用,同时带上表示用户会话的访问令牌和刷新令牌。
在 OAuth 流程中无法获取全名
在使用 OAuth 流程时,无法从 Apple 的响应中获取用户的全名。Apple 仅在首次登录时通过原生身份验证方式(Sign in with Apple JS 或原生 iOS/macOS SDK)提供全名。
如果你需要收集用户名,可以考虑:
- 改用 Apple JS 登录(见下文)
- 通过单独的入职表格收集名称
- 使用个人资料表格来存储用户信息
以 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}配置 #
你需要以下信息:
- 你的 Apple 开发者账号的 团队 ID,这是一个由 10 个字符组成的字母数字串,用于唯一标识应用的开发者。通常可以在 Apple 开发者控制台右上角的菜单中找到。
- 在 Apple 开发者控制台的 服务 部分为“使用 Apple 登录进行邮件通信”注册邮箱来源。这可以让 Apple 在用户选择隐藏邮箱地址时,通过你的域发送中转邮件。
- 一个应用 ID,用于唯一标识你正在构建的应用。你可以在 Apple 开发者控制台的 Identifiers 部分创建一个新的应用 ID(使用右上角的筛选菜单可以查看所有应用 ID)。这些通常是反向域名字符串,例如
com.example.app。创建应用 ID 后,别忘了在功能列表中配置“使用 Apple 登录”。目前 Supabase Auth 不支持服务器到服务器的通知端点,所以这项设置可以留空。(过去,应用 ID 被称为 bundle ID。) - 一个 服务 ID,用来唯一标识你在上一步注册的应用提供的网络服务。你可以在 Apple 开发者后台的 标识符 页面创建新的服务 ID(使用右上角的筛选菜单可以查看所有服务 ID)。它们通常是一个反向域名字符串,例如
com.example.app.web。 - 为新创建的 Services ID 配置网站 URL。你应该使用的网页域名是你的 Supabase 项目所在的域名。通常这是
<project-id>.supabase.co,而重定向 URL 是https://<project-id>.supabase.co/auth/v1/callback。 - 在 Apple 开发者控制台的 Keys 部分创建一个签名 Key。你可以使用这个密钥通过下面的工具生成一个密钥,然后将其添加到你的 Supabase 项目的 Auth 配置中。务必妥善保存
AuthKey_XXXXXXXXXX.p8文件。如果你不小心丢失或意外公开了它,请立即在 Apple 开发者控制台撤销并创建一个新的。 - 最后,将你在上面配置的信息添加到 Supabase 控制台中的 Apple 提供商配置 中。如果你的项目也使用原生的“使用 Apple 登录”(例如在 iOS、Expo 或 Flutter 上),请将这个服务 ID 列为 Client IDs 字段的第一个条目。Supabase 会在列表中使用第一个客户端 ID 来处理网页
signInWithOAuth流程,而原生的signInWithIdToken流程则接受列表中的任意客户端 ID 作为有效的令牌受众,不管顺序如何。如果原生 App ID 放在服务 ID 前面,原生登录仍然可以使用,但网页登录会被 Apple 拒绝。
你也可以使用管理 API 配置苹果认证提供商:
🌐 You can also configure the Apple auth provider using the Management API:
1# Get your access token from https://supabase.com/dashboard/account/tokens2export SUPABASE_ACCESS_TOKEN="your-access-token"3export PROJECT_REF="your-project-ref"45# Configure Apple auth provider6curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \7 -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \8 -H "Content-Type: application/json" \9 -d '{10 "external_apple_enabled": true,11 "external_apple_client_id": "your-services-id",12 "external_apple_secret": "your-generated-secret-key"13 }'使用这个工具生成新的 Apple 客户端密钥。没有任何密钥会离开你的浏览器!请注意,这个工具目前在 Safari 上无法使用,所以请改用 Firefox 或基于 Chrome 的浏览器。
使用 Apple JS 登录#
使用 Apple JS 登录 是苹果官方提供的用于在网站上认证 Apple 用户的框架。虽然它可以用于基于网页的应用,但对于那些场景,上面描述的 OAuth 流程会更有优势。我们建议只在传统网站上使用这种方法。
一旦用户通过 Apple JS 登录同意,你可以在网站上使用 Supabase JavaScript 库的 signInWithIdToken() 方法来获取访问令牌和刷新令牌:
1async function signIn() {2 try {3 // Generate a nonce for security4 const nonce = crypto.randomUUID() // or use your preferred nonce generation method56 const data = await AppleID.auth.signIn()78 const { data: authData, error } = await supabase.auth.signInWithIdToken({9 provider: 'apple',10 token: data.id_token,11 nonce: nonce,12 })1314 if (error) {15 throw error16 }1718 // Apple only provides the user's name on the first sign-in19 // The user object contains name information from Apple's response20 if (data.user && data.user.name) {21 const fullName = [22 data.user.name.firstName,23 data.user.name.middleName,24 data.user.name.lastName25 ].filter(Boolean).join(' ')2627 // Save the name to user metadata for future use28 await supabase.auth.updateUser({29 data: {30 full_name: fullName,31 given_name: data.user.name.firstName,32 family_name: data.user.name.lastName,33 }34 })35 }36 } catch (error) {37 console.error('Apple sign in failed:', error)38 // Handle sign-in errors appropriately39 }40}或者,你可以使用带有 usePopup 选项的 AppleIDSignInOnSuccess 事件:
1// Generate and store nonce for verification2const nonce = crypto.randomUUID()34// Initialize Apple ID with nonce5AppleID.auth.init({6 clientId: 'your-services-id',7 scope: 'name email',8 redirectURI: 'https://your-domain.com/auth/callback',9 usePopup: true,10 nonce: nonce,11})1213// Listen for authorization success14document.addEventListener('AppleIDSignInOnSuccess', async (event) => {15 try {16 const { data: authData, error } = await supabase.auth.signInWithIdToken({17 provider: 'apple',18 token: event.detail.authorization.id_token,19 nonce: nonce,20 })2122 if (error) {23 throw error24 }2526 // Apple only provides the user's name on the first sign-in27 if (event.detail.user && event.detail.user.name) {28 const fullName = [29 event.detail.user.name.firstName,30 event.detail.user.name.middleName,31 event.detail.user.name.lastName32 ].filter(Boolean).join(' ')3334 // Save the name to user metadata for future use35 await supabase.auth.updateUser({36 data: {37 full_name: fullName,38 given_name: event.detail.user.name.firstName,39 family_name: event.detail.user.name.lastName,40 }41 })42 }43 } catch (error) {44 console.error('Apple sign in failed:', error)45 }46})初始化库时,确保像上面的例子一样请求范围 name email。
配置 #
要使用 Apple JS 登录,你需要配置这些选项:
- 拥有一个 App ID 来唯一标识你正在构建的应用。你可以在 Apple 开发者控制台的 Identifiers 部分创建一个新的 App ID(使用右上角的筛选菜单可以查看所有 App ID)。这些通常是倒置的域名字符串,例如
com.example.app。确保你在创建的或已有的 App ID 的功能列表中配置了“使用 Apple 登录”。目前 Supabase Auth 不支持服务器到服务器的通知端点,所以你可以把该设置留空。(以前 App ID 被称为 bundle ID。) - 获取一个附加到 App ID 的 服务 ID,它可以唯一标识网站。在初始化使用 Apple JS 的登录时,把这个值作为客户端 ID。你可以在 Apple 开发者控制台的 Identifiers 部分创建一个新的服务 ID(使用右上角的筛选菜单可以看到所有服务 ID)。这些通常是一个反向域名字符串,例如
com.example.app.website。 - 为新创建的 Services ID 配置网站 URL。你应该使用托管你网站的域名。重定向 URL 也必须指向你网站上的一个页面,用于接收来自 Apple 的回调。
- 将你创建的服务 ID 注册到你项目在 Supabase 仪表板中的 Apple 提供者配置 下的 客户端 ID。
如果你正在使用 Apple JS 登录,你不需要配置 OAuth 设置。