在 Flutter 中用 Apple 登录接入 Supabase Auth(分步指南)
本文说明如何在 Flutter 应用里使用 Sign in with Apple(Apple 登录),并通过 Supabase Auth 建立会话。在 iOS / macOS 上,推荐用 sign_in_with_apple 走系统原生授权,取得 ID Token 后调用 signInWithIdToken;在 Android、Web、Windows、Linux 等没有原生 Apple 登录的环境,需改用 Supabase 的 signInWithOAuth 浏览器流程,并完成 Apple 侧的 Services ID、密钥与回调 URL 等配置。
官方参考:Supabase · Apple 登录(含 Flutter)、sign_in_with_apple。
你将完成什么
- 在 Apple Developer 为 App 开启 Sign in with Apple(必要时配置 Services ID 与密钥,用于 OAuth 流程)
- 在 Supabase 启用 Apple 提供商,并把 Bundle ID(及多环境变体) 登记到 Client IDs
- 在 Flutter 中:iOS/macOS 使用
signInWithIdToken;其他平台使用signInWithOAuth与深度链接(如需要) - (可选)在首次登录时把 Apple 返回的姓名写入用户 metadata(Apple 只在第一次授权时提供全名)
前置条件
- 已能运行 Flutter 工程,并已集成
supabase_flutter与Supabase.initialize(可参考本站《在 Flutter 中接入 Supabase 邮箱登录》中的初始化步骤)。 - iOS / macOS:Apple Developer 账号;Xcode 中正确配置 Signing & Capabilities。
- 若使用 OAuth / Flutter Web / Android 上的 Apple 登录:需在 Apple Developer 配置 Services ID、签名密钥(.p8),并在 Supabase 填写由密钥生成的 Secret;该 Secret 最长约 6 个月需轮换一次,请设日历提醒。
- 纯原生 iOS/macOS 客户端:按 Supabase 文档说明,可以不配置 OAuth 那一套(Services ID、p8 等),但仍须在控制台 Apple 提供商 中登记 Client IDs(即 Bundle ID) 并启用提供商。
重要概念:两种接入方式
| 场景 | 做法 | Supabase / Apple 配置要点 |
|---|---|---|
| iOS、macOS 原生 App | sign_in_with_apple → signInWithIdToken | 登记 Bundle ID 到 Client IDs;纯原生可不配 OAuth Secret |
| Android、Web、桌面 等 | signInWithOAuth(OAuthProvider.apple, …) | 需 Services ID、回调 https://<project-ref>.supabase.co/auth/v1/callback、.p8 与 Secret;移动端要配 深度链接 |
不要在非 Apple 平台上按 sign_in_with_apple 的 README 去配「Android/Web 的 sign_in_with_apple」来接 Supabase;这些平台应走 OAuth。
第一步:Apple Developer — App ID 与 Sign in with Apple
- 登录 Apple Developer,打开 Certificates, Identifiers & Profiles。
- Identifiers → 筛选 App IDs,选择或新建你的应用标识(例如
com.example.app)。 - 在 Capabilities 中勾选 Sign In with Apple,保存。
- Server-to-Server Notification Endpoint:当前 Supabase Auth 不支持该端点配置,相关项可留空(与官方文档一致)。
若你还有 开发 / 预览 / 生产 等多个 Bundle ID,每个要在 Supabase 的 Apple Client IDs 里各加一条(与 Expo 文档同理,Flutter 多 flavor 也一样)。
第二步:Xcode / Flutter iOS 工程
- 用 Xcode 打开
ios/Runner.xcworkspace。 - 选中 Runner target → Signing & Capabilities → + Capability → 添加 Sign In with Apple(若上一步 App ID 已开启,能力与描述文件应能正常生效)。
- 确认 Bundle Identifier 与 Apple Developer 里登记的 App ID 完全一致。
sign_in_with_apple 的 iOS/macOS 具体 plist、最低系统版本等以 pub.dev 说明 为准。
第三步:Supabase — 启用 Apple 并登记 Client IDs
- Supabase 控制台 → Authentication → Providers → Apple。
- 打开 Enable Sign in with Apple。
- 在 Client IDs(或控制台中等价字段)中加入你的 Bundle ID(及所有需要登录的变体,如
com.example.app.dev)。 - 仅在使用 OAuth时(Web、Android、或
signInWithOAuth):再填写 Services ID、Secret Key(由 .p8 等生成的客户端密钥)、Team ID、Key ID 等,并按文档把 Apple Services ID 的 Return URLs 配成:
https://<你的-project-ref>.supabase.co/auth/v1/callback - Authentication → URL Configuration:Site URL、Redirect URLs 与深度链接 /网站回调一致,避免 OAuth 回调被拒绝。
第四步:Flutter 依赖
在 pubspec.yaml 中(版本以 pub.dev 为准):
dependencies:
flutter:
sdk: flutter
supabase_flutter: ^2.8.0
sign_in_with_apple: ^6.1.0
crypto: ^3.0.0
flutter pub get
crypto 用于对 nonce 做 SHA256,与 sign_in_with_apple 传入的 nonce 及 Supabase 校验一致。
第五步:iOS / macOS — 原生 Apple 登录代码示例
逻辑要点:
- 使用
supabase.auth.generateRawNonce()生成 原始 nonce。 - 对原始 nonce 做 SHA256 十六进制字符串,传给
SignInWithApple.getAppleIDCredential(nonce: hashedNonce)。 - 将凭证中的 identityToken 与 原始 nonce 传给
signInWithIdToken(provider: OAuthProvider.apple, …)。 - 若
givenName/familyName非空(通常仅首次授权有值),可updateUser写入 metadata。
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
final supabase = Supabase.instance.client;
/// 在 iOS / macOS 上使用原生 Sign in with Apple,并接入 Supabase 会话。
Future<AuthResponse> signInWithAppleNative() async {
final rawNonce = supabase.auth.generateRawNonce();
final hashedNonce = sha256.convert(utf8.encode(rawNonce)).toString();
final credential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
nonce: hashedNonce,
);
final idToken = credential.identityToken;
if (idToken == null) {
throw const AuthException('无法从 Apple 凭证中读取 ID Token。');
}
final authResponse = await supabase.auth.signInWithIdToken(
provider: OAuthProvider.apple,
idToken: idToken,
nonce: rawNonce,
);
if (credential.givenName != null || credential.familyName != null) {
final parts = <String>[];
if (credential.givenName != null) parts.add(credential.givenName!);
if (credential.familyName != null) parts.add(credential.familyName!);
final fullName = parts.join(' ');
await supabase.auth.updateUser(
UserAttributes(
data: {
'full_name': fullName,
if (credential.givenName != null) 'given_name': credential.givenName,
if (credential.familyName != null) 'family_name': credential.familyName,
},
),
);
}
return authResponse;
}
界面上可用 SignInWithAppleButton 触发上述方法;注意仅在 iOS 13+ / macOS 10.15+ 等支持的环境展示。
第六步:Android / Web / 桌面 — OAuth 流程
使用 signInWithOAuth,并配置 redirectTo(非 Web 时常为自定义 URL Scheme),且完成上文 OAuth 所需的 Apple Services ID + Secret 与 Supabase Redirect URLs。
import 'package:flutter/foundation.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
Future<void> signInWithAppleOAuth() async {
await Supabase.instance.client.auth.signInWithOAuth(
OAuthProvider.apple,
redirectTo: kIsWeb ? null : '你的scheme://你的host', // 与深度链接、控制台 Redirect URLs 一致
authScreenLaunchMode: kIsWeb
? LaunchMode.platformDefault
: LaunchMode.externalApplication,
);
}
深度链接与 app_links / uni_links 等配合方式见:Supabase · 原生移动深度链接。
第七步:登出与其他 Supabase 登录方式相同,例如:
await Supabase.instance.client.auth.signOut();
常见问题
| 现象 | 建议排查 |
|---|---|
signInWithIdToken 报错 / 校验失败 | Client IDs 是否包含当前 Bundle ID;nonce 是否为「原始值 + SHA256 给 Apple」这一对 |
| OAuth 回调失败 | Services ID 的 Return URL 是否精确包含 https://<ref>.supabase.co/auth/v1/callback;Supabase Redirect URLs 是否包含你的 redirectTo |
| 生产环境突然全部无法登录 Apple | OAuth Secret 是否过期(约 6 个月);.p8 是否泄露需轮换 |
| 用户姓名始终为空 | Apple 只在首次授权通过原生接口返回姓名;需在首次登录时写入 metadata,或引导用户在资料页自行补充 |
合规与设计提示
- 若 App 已提供第三方登录(如 Google),在 iOS 上通常须同时提供 Sign in with Apple(以 App Store 审核指南为准)。
- 用户选择「隐藏邮箱」时,会得到 Apple 中转邮箱;需在产品中说明邮件送达与账号找回方式。
按上述步骤,你可以在 Flutter 中完成 Apple 登录与 Supabase Auth 的串联;以 原生 iOS/macOS 为主时优先 signInWithIdToken,跨平台需求再补 OAuth 与密钥维护流程即可。