关于 auth0-flutter-native
为 iOS 和 Android 上的 Flutter 应用提供 Auth0 原生认证集成指导,适用于需要登录、会话恢复和凭据安全存储的开发者。它利用系统浏览器进行认证,并通过平台安全存储管理令牌,可选用生物识别保护凭据。
主要功能
- 01集成官方 auth0_flutter SDK
- 02通过系统浏览器进行认证
- 03使用凭据管理器安全存储令牌
- 04恢复会话并续期凭据
- 05可选生物识别保护
适用场景
- 01为 Flutter 移动应用添加 Auth0 登录
- 02保存并恢复用户认证会话
- 03使用生物识别保护已存储凭据
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ auth0/ agent-skills --skill auth0-flutter-native安装后在 Claude Code 中输入/auth0-flutter-native确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/api.mdintegration.mdsetup.mdtests/benchmark-config.jsonevals.jsongraders.jsongraders.tspackage.jsonrun-evals.mjs详细介绍
Auth0 Flutter 原生(iOS/Android)集成
auth0_flutter 是适用于 Flutter 应用的官方 Auth0 SDK。在**移动设备(iOS/Android)**上,它通过设备的系统浏览器(Web Auth / Universal Login)执行身份验证,并通过内置的 CredentialsManager 将令牌安全地存储在平台钥匙串/密钥库中,同时支持可选的生物识别保护。
**Agent 指令:**提供 SDK 设置说明前,运行以下命令之一获取最新发布版本:
gh api repos/auth0/auth0-flutter/releases/latest --jq '.tag_name'flutter pub info auth0_flutter 2>/dev/null | head -5或查看 pub.dev:
curl -s https://pub.dev/api/packages/auth0_flutter | python3 -c "import sys,json;print(json.load(sys.stdin)['latest']['version'])"在所有依赖项行中使用返回的版本,而不是下面任何硬编码的版本。当前已知版本:
2.1.0。
不适用场景
- Flutter Web:使用 auth0-flutter-web — Web 使用不同的平台接口(
Auth0Web),封装了 Auth0 SPA JS - 原生 iOS(Swift,无 Flutter):使用 auth0-swift
- 原生 Android(Kotlin/Java,无 Flutter):使用 auth0-android
- React Native:使用 auth0-react-native
- React SPA:使用 auth0-react
- Node.js/Express 服务端:使用 auth0-express
前置条件
- Flutter 3.24.0+
- Dart 3.5.0+
- Android:minSdkVersion 21+,compileSdkVersion 34+
- iOS:14.0+(Universal Link 回调要求 iOS 17.4+)
- Auth0 账户 — 免费注册
- Auth0 CLI — 安装说明(用于创建和配置 Auth0 应用)
快速入门工作流
**Agent 指令:**按顺序执行这些步骤。如果任何步骤遇到错误,先尝试修复,最多重试 5 次,然后再调用
AskUserQuestion向用户寻求指导。始终先搜索现有代码 — 如果已有登录/登出处理程序,应接入这些处理程序,而不是新建。
步骤 1 — 安装 SDK
**Agent 指令:**检查项目目录中是否存在
pubspec.yaml。如果存在,添加依赖项。如果未找到,则说明这不是 Flutter 项目 — 请询问用户。在项目根目录中运行:
flutter pub add auth0_flutter确认已将依赖项添加到
pubspec.yaml:dependencies: auth0_flutter: ^2.1.0
步骤 2 — 配置 Auth0
注意:Auth0 Domain 和 Client ID 是公开配置(不是机密)— 原生应用使用 PKCE,无需客户端密钥。将它们直接传递给
Auth0(domain, clientId)即可;无需将它们存储在环境变量中或进行隐藏。Agent 指令:
- **如果用户的提示词中已包含 Auth0 凭据(Domain 和 Client ID):**在
Auth0(...)构造函数中直接使用这些值,然后继续执行步骤 3。- **如果未提供凭据:**使用
AskUserQuestion询问用户希望采用哪种设置方式:"如何设置 Auth0 应用 — 自动方式(我运行 Auth0 CLI 创建应用)还是手动方式(你在 Auth0 Dashboard 中创建应用并提供 Domain + Client ID)?"
- **自动方式:**按照设置指南中的 Auth0 CLI 步骤创建 Native 应用。
- **手动方式:**向用户索取 Auth0 Domain 和 Client ID,并直接使用。
按照设置指南 — Auth0 配置完成预检查并运行
auth0 apps create命令。
步骤 3 — 配置 Android
**Agent 指令:**编辑
android/app/build.gradle(或build.gradle.kts),并在android { defaultConfig { ... } }中添加manifestPlaceholders。这些占位符会提供 SDK 的RedirectActivityintent filter 注册的回调 URL — 没有它们,应用将无法正确构建以用于 Auth0。
对于 android/app/build.gradle(Groovy):
android {
defaultConfig {
manifestPlaceholders = [auth0Domain: "YOUR_AUTH0_DOMAIN", auth0Scheme: "https"]
}
}
对于 android/app/build.gradle.kts(Kotlin DSL):
android {
defaultConfig {
manifestPlaceholders["auth0Domain"] = "YOUR_AUTH0_DOMAIN"
manifestPlaceholders["auth0Scheme"] = "https"
}
}
**Agent 指令:**使用
auth0Scheme: "https"来使用 Android App Links(推荐)。如果应用改用自定义 scheme,请将其设置为小写的 scheme 字符串,并在 Dart 中将相同的 scheme 传递给webAuthentication(scheme: ...)。详情请参阅设置指南。
步骤 4 — 配置 iOS
**Agent 指令:**对于 iOS 17.4 及更高版本的默认 HTTPS(Universal Link)流程,无需更改
Info.plist,但必须在 Xcode 中添加 Associated Domains 功能(webcredentials:YOUR_AUTH0_DOMAIN)。对于较旧的 iOS 版本或自定义 URL scheme,请在ios/Runner/Info.plist中添加CFBundleURLTypes条目:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>None</string>
<key>CFBundleURLName</key>
<string>auth0</string>
<key>CFBundleURLSchemes</key>
<array>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</array>
</dict>
</array>
步骤 5 — 配置回调 URL
**Agent 指令:**使用 Auth0 CLI 注册特定于平台的回调 URL 和注销 URL。确定 Android 包名(从
android/app/build.gradle的applicationId获取)和 iOS bundle 标识符(从 Xcode /PRODUCT_BUNDLE_IDENTIFIER获取),然后运行以下命令,并将占位符(CLIENT_ID、YOUR_DOMAIN、ANDROID_PACKAGE_NAME、IOS_BUNDLE_ID)替换为项目中的值:auth0 apps update CLIENT_ID \ --callbacks "https://YOUR_DOMAIN/android/ANDROID_PACKAGE_NAME/callback,https://YOUR_DOMAIN/ios/IOS_BUNDLE_ID/callback" \ --logout-urls "https://YOUR_DOMAIN/android/ANDROID_PACKAGE_NAME/callback,https://YOUR_DOMAIN/ios/IOS_BUNDLE_ID/callback" \ --no-input
回调 URL 格式如下:
- Android:
https://YOUR_DOMAIN/android/YOUR_PACKAGE_NAME/callback - iOS:
https://YOUR_DOMAIN/ios/YOUR_BUNDLE_ID/callback
步骤 6 — 实现身份验证
**Agent 指令:**在项目中搜索主应用入口点(
main.dart)。确定所使用的状态管理方式:
- 查找
provider、riverpod、bloc、GetX或mobx导入- 如果都没有找到,则使用带
setState的基本StatefulWidget然后仅遵循下方匹配的路径。如果不确定,请通过
AskUserQuestion提问:“你的 Flutter 应用使用哪种状态管理方式 — Provider、Riverpod、Bloc 还是基本的 setState?”
基本 StatefulWidget(默认)
**Agent 指令:**创建一个
AuthService类,然后将其接入应用的根 widget。搜索MaterialApp或CupertinoAppwidget 并相应更新。应用启动时,从CredentialsManager缓存中恢复会话。
// lib/auth_service.dart
import 'package:auth0_flutter/auth0_flutter.dart';
class AuthService {
late final Auth0 _auth0;
Credentials? _credentials;
AuthService({required String domain, required String clientId}) {
_auth0 = Auth0(domain, clientId);
}
bool get isAuthenticated => _credentials != null;
UserProfile? get user => _credentials?.user;
/// Restore a stored session on app startup, if one exists.
Future<void> init() async {
final hasValid = await _auth0.credentialsManager.hasValidCredentials();
if (hasValid) {
_credentials = await _auth0.credentialsManager.credentials();
}
}
/// Launch Web Auth via the system browser. Tokens are stored automatically.
Future<void> login() async {
_credentials = await _auth0
.webAuthentication()
.login(scopes: {'openid', 'profile', 'email', 'offline_access'});
}
/// Clear the session in the browser and wipe stored credentials.
Future<void> logout() async {
await _auth0.webAuthentication().logout();
await _auth0.credentialsManager.clearCredentials();
_credentials = null;
}
}
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:auth0_flutter/auth0_flutter.dart'; // for WebAuthenticationException
import 'auth_service.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _authService = AuthService(
domain: 'YOUR_AUTH0_DOMAIN',
clientId: 'YOUR_AUTH0_CLIENT_ID',
);
bool _isLoading = true;
@override
void initState() {
super.initState();
_initAuth();
}
Future<void> _initAuth() async {
await _authService.init();
setState(() => _isLoading = false);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: _isLoading
? const Scaffold(body: Center(child: CircularProgressIndicator()))
: _authService.isAuthenticated
? HomeScreen(authService: _authService, onChanged: _refresh)
: LoginScreen(authService: _authService, onChanged: _refresh),
);
}
void _refresh() => setState(() {});
}
class LoginScreen extends StatelessWidget {
final AuthService authService;
final VoidCallback onChanged;
const LoginScreen({super.key, required this.authService, required this.onChanged});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () async {
// Capture the messenger before the await to avoid using
// BuildContext across an async gap.
final messenger = ScaffoldMessenger.of(context);
try {
await authService.login();
onChanged();
} on WebAuthenticationException catch (e) {
messenger.showSnackBar(
SnackBar(content: Text('Login failed: ${e.message}')),
);
}
},
child: const Text('Log In'),
),
),
);
}
}
class HomeScreen extends StatelessWidget {
final AuthService authService;
final VoidCallback onChanged;
const HomeScreen({super.key, required this.authService, required this.onChanged});
@override
Widget build(BuildContext context) {
final user = authService.user;
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
actions: [
IconButton(
onPressed: () async {
await authService.logout();
onChanged();
},
icon: const Icon(Icons.logout),
),
],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (user?.pictureUrl != null)
CircleAvatar(
backgroundImage: NetworkImage(user!.pictureUrl.toString()),
radius: 40,
),
const SizedBox(height: 16),
Text('Welcome, ${user?.name ?? 'User'}!'),
Text(user?.email ?? ''),
],
),
),
);
}
}
Provider(状态管理)
**Agent 指令:**如果项目使用
provider,则将AuthService创建为ChangeNotifier,并通过应用根节点处的ChangeNotifierProvider注入。
// lib/auth_service.dart
import 'package:flutter/foundation.dart';
import 'package:auth0_flutter/auth0_flutter.dart';
class AuthService extends ChangeNotifier {
late final Auth0 _auth0;
Credentials? _credentials;
bool _isLoading = true;
AuthService({required String domain, required String clientId}) {
_auth0 = Auth0(domain, clientId);
}
bool get isAuthenticated => _credentials != null;
bool get isLoading => _isLoading;
UserProfile? get user => _credentials?.user;
Future<void> init() async {
if (await _auth0.credentialsManager.hasValidCredentials()) {
_credentials = await _auth0.credentialsManager.credentials();
}
_isLoading = false;
notifyListeners();
}
Future<void> login() async {
_credentials = await _auth0
.webAuthentication()
.login(scopes: {'openid', 'profile', 'email', 'offline_access'});
notifyListeners();
}
Future<void> logout() async {
await _auth0.webAuthentication().logout();
await _auth0.credentialsManager.clearCredentials();
_credentials = null;
notifyListeners();
}
}
// lib/main.dart — wrap with ChangeNotifierProvider
import 'package:provider/provider.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => AuthService(
domain: 'YOUR_AUTH0_DOMAIN',
clientId: 'YOUR_AUTH0_CLIENT_ID',
)..init(),
child: const MyApp(),
),
);
}
如需了解 Riverpod、Bloc、生物识别和高级场景的完整模式,请参阅集成模式。
步骤 7 — 验证构建
**Agent 指令:**运行构建,验证集成能够成功编译且没有错误:
flutter build apk --debug # Android flutter build ios --no-codesign # iOS (on macOS)然后在设备或模拟器上运行应用进行测试:
flutter run如果构建失败,请查看错误消息并修复,最多尝试 5 次,然后再询问用户。
**真机测试:**无法在模拟器上测试生物识别保护(Face ID / Touch ID / 指纹)——iOS Simulator 和 Android 模拟器的生物识别硬件功能有限或完全不具备。发布前,请在真实设备上测试生物识别和完整的 Universal Login 重定向。
详细文档
- 设置指南 — 通过 Auth0 CLI 创建 Auth0 应用、Android
manifestPlaceholders、iOSInfo.plist/ Associated Domains、注册回调 URL - 集成模式 — Web Auth 登录/登出、CredentialsManager、生物识别保护、自定义 scheme、组织、API 访问令牌、状态管理模式、错误处理
- API 参考与测试 — 完整 API 参考、配置选项、声明参考、测试清单、故障排查
常见错误
| 错误 | 修正方法 |
|---|---|
| Auth0 应用类型未设置为 Native | 使用 auth0 apps create --type native 创建应用(或在 Auth0 Dashboard 中选择“Native”) |
Android 缺少 manifestPlaceholders | 将 manifestPlaceholders = [auth0Domain: "...", auth0Scheme: "https"] 添加到 android/app/build.gradle 的 defaultConfig 中——否则构建会失败 |
在移动端使用 Auth0Web | 移动端使用带有 webAuthentication() 的 Auth0 类,而不是 Auth0Web(后者是仅适用于 Web 的 API) |
在移动端导入 auth0_flutter_web.dart | 仅导入 package:auth0_flutter/auth0_flutter.dart——_web 导入用于 Flutter Web |
| 回调 URL 不匹配 | 在 Allowed Callback URLs 中注册 https://YOUR_DOMAIN/android/PACKAGE_NAME/callback 和 https://YOUR_DOMAIN/ios/BUNDLE_ID/callback |
| Gradle 与 Dart 中的 scheme 不匹配 | 如果 auth0Scheme 是自定义 scheme,请将相同的值传给 webAuthentication(scheme: 'myscheme') |
| Android 自定义 scheme 中包含大写字母 | Android 自定义 scheme 必须全部使用小写字母 |
| Android 上从未出现生物识别提示 | 为使生物识别提示正常工作,MainActivity 必须继承 FlutterFragmentActivity(而非 FlutterActivity) |
| 登录后未存储凭据 | webAuthentication().login() 会自动存储凭据;不要重复存储,除非通过 api.renewCredentials 手动续期 |
| 启动时未恢复会话 | 在 initState() 中调用 credentialsManager.hasValidCredentials() 和 credentials() 以恢复会话 |
缺少 offline_access scope | 将 'offline_access' 添加到 scope 中,以便 CredentialsManager 使用 refresh token 静默续期已过期的访问令牌 |
捕获通用 Exception | 捕获 WebAuthenticationException(登录/登出)和 CredentialsManagerException(凭据错误),并检查 isUserCancelledException、isNoCredentialsFound、isTokenRenewFailed 等 |
相关技能
- auth0-quickstart — Auth0 初始设置与账户创建
- auth0-flutter-web — 同一 SDK,Web 平台
- auth0-swift — 原生 iOS(Swift)
- auth0-android — 原生 Android(Kotlin/Java)
快速参考
| API | 用途 |
|---|---|
Auth0(domain, clientId) | 创建 SDK 客户端 |
auth0.webAuthentication().login(...) | 在系统浏览器中启动 Universal Login |
auth0.webAuthentication().logout() | 清除浏览器会话 |
auth0.webAuthentication(scheme: '...') | 使用自定义 URL scheme |
auth0.credentialsManager.credentials() | 获取已存储的凭据(过期时自动续期) |
auth0.credentialsManager.hasValidCredentials() | 检查是否存在有效的已存储会话 |
auth0.credentialsManager.clearCredentials() | 清除已存储的凭据 |
LocalAuthentication(title: ...) | 启用对已存储凭据的生物识别保护 |