mcpField
A
auth0-flutter-native

auth0-flutter-native

github.com/auth0安全55245 次安装1 天前更新
代码仓库

关于 auth0-flutter-native

为 iOS 和 Android 上的 Flutter 应用提供 Auth0 原生认证集成指导,适用于需要登录、会话恢复和凭据安全存储的开发者。它利用系统浏览器进行认证,并通过平台安全存储管理令牌,可选用生物识别保护凭据。

主要功能

  1. 01集成官方 auth0_flutter SDK
  2. 02通过系统浏览器进行认证
  3. 03使用凭据管理器安全存储令牌
  4. 04恢复会话并续期凭据
  5. 05可选生物识别保护

适用场景

  1. 01为 Flutter 移动应用添加 Auth0 登录
  2. 02保存并恢复用户认证会话
  3. 03使用生物识别保护已存储凭据

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/auth0/agent-skills --skill auth0-flutter-native

安装后在 Claude Code 中输入/auth0-flutter-native确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
api.md
integration.md
setup.md
tests/
benchmark-config.json
evals.json
graders.json
graders.ts
package.json
run-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 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 的 RedirectActivity intent 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 或 CupertinoApp widget 并相应更新。应用启动时,从 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、iOS Info.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 等

相关技能

快速参考

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: ...)启用对已存储凭据的生物识别保护

参考资料

评论

支持 Markdown