mcpField
A
auth0-flutter-web

auth0-flutter-web

github.com/auth0开发55203 次安装1 天前更新
代码仓库

关于 auth0-flutter-web

auth0-flutter-web 是用于 Flutter Web 应用的 Auth0 集成技能,指导开发者配置 SDK 并实现浏览器身份验证。它涵盖重定向和弹窗登录、凭据缓存及令牌续期。

主要功能

  1. 01配置 Auth0 Flutter SDK
  2. 02浏览器重定向与弹窗登录
  3. 03凭据缓存和令牌续期
  4. 04适配不同状态管理方式
  5. 05构建验证与故障排查

适用场景

  1. 01为 Flutter Web 应用添加 Auth0 登录
  2. 02按现有状态管理方案接入认证
  3. 03配置回调地址和 Web 来源
  4. 04检查构建及令牌续期问题

安装

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

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

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

包含的文件

除 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 Web 集成

auth0_flutter 是适用于 Flutter 应用的官方 Auth0 SDK。在 Web 平台上,它封装了 Auth0 SPA JS SDK,通过重定向或弹出窗口流程提供基于浏览器的身份验证,并内置凭据缓存和自动令牌续期功能。

Agent 指令: 当前 SDK 版本为 2.1.0。请在以下所有依赖项声明中使用此版本。如需核实,请运行 flutter pub add auth0_flutter,该命令会自动解析最新的兼容版本。

不适用场景

前置条件

  • Flutter 3.24.0+
  • Dart 3.5.0+
  • 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 属于公开配置(不是机密信息)。SPA 使用 PKCE,且无需客户端密钥,这些值会随浏览器代码包一起发布。请直接将它们传递给 Auth0Web(domain, clientId)——无需将它们存储在环境变量中,也无需隐藏。

Agent 指令:

  • 如果用户的提示词中已提供 Auth0 凭据(domain 和 client ID): 转到步骤 3,并在 Auth0Web(...) 构造函数中直接使用这些值。
  • 如果未提供凭据: 使用 Auth0 CLI 创建应用——不要要求用户在 Dashboard 中手动创建或配置 Auth0 应用。始终使用 CLI 方式。

请按照设置指南 — Auth0 配置完成预检,并运行 auth0 apps create 命令。

步骤 3 — 将 Auth0 SPA JS 添加到 index.html

Agent 指令:

  1. 找到 Web 入口文件:web/index.html
  2. 在 <head> 中添加 Auth0 SPA JS 脚本标签:
<script src="https://cdn.auth0.com/js/auth0-spa-js/2.1/auth0-spa-js.production.js" defer></script>

如果 web/index.html 不存在,用户可能需要运行 flutter create . --platforms=web 来添加 Web 支持。

步骤 4 — 配置回调 URL

Agent 指令:

  1. 确定应用的 Web URL。本地开发时,默认值为 http://localhost:3000(使用 --web-port 3000 运行时)。
  2. 通过 AskUserQuestion 询问用户:"你将在本地使用哪个端口运行 Flutter Web 应用?(默认:3000)"
  3. 使用 Auth0 CLI 注册回调 URL(将 CLIENT_ID、APP_URL 替换为实际值):
auth0 apps update CLIENT_ID \
  --callbacks "APP_URL" \
  --logout-urls "APP_URL" \
  --web-origins "APP_URL" \
  --no-input

对于生产环境,也请将生产 URL 添加到每个列表中。

步骤 5 — 实现身份验证

Agent 指令: 在项目中搜索主应用入口点(main.dart)。确定状态管理方式:

  • 查找 provider、riverpod、bloc、GetX 或 mobx 导入
  • 如果没有找到,则使用基本的 StatefulWidget 和 setState

然后仅遵循下面匹配的路径。如果不明确,请通过 AskUserQuestion 提问:“你的 Flutter 应用使用哪种状态管理方式 — Provider、Riverpod、Bloc,还是基本的 setState?”

基本的 StatefulWidget(默认)

Agent 指令: 创建一个 AuthService 类,然后将其接入应用的根组件。搜索 MaterialApp 或 CupertinoApp 组件并相应地进行更新。

// lib/auth_service.dart
import 'package:auth0_flutter/auth0_flutter.dart';
import 'package:auth0_flutter/auth0_flutter_web.dart';

class AuthService {
  late final Auth0Web _auth0;
  Credentials? _credentials;

  AuthService({required String domain, required String clientId}) {
    _auth0 = Auth0Web(domain, clientId);
  }

  bool get isAuthenticated => _credentials != null;
  Credentials? get credentials => _credentials;
  UserProfile? get user => _credentials?.user;

  /// Call on app startup to restore session from cache
  Future<void> onLoad() async {
    _credentials = await _auth0.onLoad();
  }

  /// Redirect to Auth0 Universal Login
  Future<void> loginWithRedirect({String? redirectUrl}) async {
    await _auth0.loginWithRedirect(
      redirectUrl: redirectUrl,
      scopes: {'openid', 'profile', 'email', 'offline_access'},
    );
  }

  /// Open Auth0 login in a popup window
  Future<void> loginWithPopup() async {
    _credentials = await _auth0.loginWithPopup(
      scopes: {'openid', 'profile', 'email', 'offline_access'},
    );
  }

  /// Get cached credentials (auto-refreshes if expired)
  Future<Credentials> getCredentials() async {
    final creds = await _auth0.credentials();
    _credentials = creds;
    return creds;
  }

  /// Check if valid credentials exist
  Future<bool> hasValidCredentials() async {
    return await _auth0.hasValidCredentials();
  }

  /// Logout and redirect back to the app
  Future<void> logout({String? returnToUrl}) async {
    await _auth0.logout(returnToUrl: returnToUrl);
    _credentials = null;
  }
}
// lib/main.dart
import 'package:flutter/material.dart';
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.onLoad();
    setState(() => _isLoading = false);
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: _isLoading
          ? const Scaffold(body: Center(child: CircularProgressIndicator()))
          : _authService.isAuthenticated
              ? HomeScreen(authService: _authService)
              : LoginScreen(authService: _authService),
    );
  }
}

class LoginScreen extends StatelessWidget {
  final AuthService authService;
  const LoginScreen({super.key, required this.authService});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ElevatedButton(
          onPressed: () => authService.loginWithRedirect(),
          child: const Text('Log In'),
        ),
      ),
    );
  }
}

class HomeScreen extends StatelessWidget {
  final AuthService authService;
  const HomeScreen({super.key, required this.authService});

  @override
  Widget build(BuildContext context) {
    final user = authService.user;
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
        actions: [
          IconButton(
            onPressed: () => authService.logout(
              returnToUrl: Uri.base.origin,
            ),
            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';
import 'package:auth0_flutter/auth0_flutter_web.dart';

class AuthService extends ChangeNotifier {
  late final Auth0Web _auth0;
  Credentials? _credentials;
  bool _isLoading = true;

  AuthService({required String domain, required String clientId}) {
    _auth0 = Auth0Web(domain, clientId);
  }

  bool get isAuthenticated => _credentials != null;
  bool get isLoading => _isLoading;
  UserProfile? get user => _credentials?.user;

  Future<void> init() async {
    _credentials = await _auth0.onLoad();
    _isLoading = false;
    notifyListeners();
  }

  Future<void> loginWithRedirect() async {
    await _auth0.loginWithRedirect(
      scopes: {'openid', 'profile', 'email', 'offline_access'},
    );
  }

  Future<void> loginWithPopup() async {
    _credentials = await _auth0.loginWithPopup(
      scopes: {'openid', 'profile', 'email', 'offline_access'},
    );
    notifyListeners();
  }

  Future<void> logout() async {
    await _auth0.logout(returnToUrl: Uri.base.origin);
    _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 和高级场景的完整模式,请参阅集成模式。

步骤 6 — 验证构建

Agent 指令: 运行构建,以验证集成是否能无错误地编译:

flutter build web

然后在本地运行应用进行测试:

flutter run -d chrome --web-port 3000

如果构建失败,请检查错误消息并修复,最多尝试 5 次,然后再询问用户。

详细文档

  • 设置指南 — 通过 Auth0 CLI 创建 Auth0 应用、设置 web/index.html、注册回调 URL
  • 集成模式 — 重定向登录、弹出窗口登录、凭据管理、状态管理模式、组织支持、错误处理
  • API 参考与测试 — 完整的 API 参考、配置选项、声明参考、测试清单、故障排除

常见错误

错误修复方法
Auth0 应用类型未设置为 Single Page Application在 Auth0 Dashboard 中创建应用时,选择“Single Page Application”
web/index.html 中缺少 Auth0 SPA JS 脚本将 <script src="https://cdn.auth0.com/js/auth0-spa-js/2.1/auth0-spa-js.production.js" defer></script> 添加到 <head>
应用启动时未调用 onLoad()始终在 initState() 或等效位置调用 onLoad(),以便在重定向后恢复会话
Auth0 Dashboard 中缺少 Allowed Web Origins将应用 URL(例如 http://localhost:3000)添加到 Allowed Web Origins — 静默续期令牌时必需
在 Web 上使用 auth0_flutter 移动端 API同时导入 package:auth0_flutter/auth0_flutter.dart(用于 Credentials、UserProfile 等类型) 和 package:auth0_flutter/auth0_flutter_web.dart(用于 Auth0Web 类)
缺少基础导入导致类型错误Credentials 和 UserProfile 从 auth0_flutter.dart 导出,而非 auth0_flutter_web.dart — 两者都需要导入
回调 URL 不匹配确保 Allowed Callback URLs 与应用运行的 URL 完全匹配(包括端口)
本地运行时未添加 --web-port使用 flutter run -d chrome --web-port 3000,确保端口与回调 URL 一致
浏览器阻止弹出窗口loginWithPopup() 必须由直接的用户交互(按钮点击)触发;不能从 initState() 调用
缺少 offline_access 范围将 'offline_access' 添加到范围集合,以启用用于静默续期的刷新令牌轮换
令牌端点出现 CORS 错误确保在 Auth0 Dashboard 中配置了 Allowed Web Origins(而不只是 Callback URLs)

参考资料

评论

支持 Markdown