关于 auth0-flutter-web
auth0-flutter-web 是用于 Flutter Web 应用的 Auth0 集成技能,指导开发者配置 SDK 并实现浏览器身份验证。它涵盖重定向和弹窗登录、凭据缓存及令牌续期。
主要功能
- 01配置 Auth0 Flutter SDK
- 02浏览器重定向与弹窗登录
- 03凭据缓存和令牌续期
- 04适配不同状态管理方式
- 05构建验证与故障排查
适用场景
- 01为 Flutter Web 应用添加 Auth0 登录
- 02按现有状态管理方案接入认证
- 03配置回调地址和 Web 来源
- 04检查构建及令牌续期问题
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ auth0/ agent-skills --skill auth0-flutter-web安装后在 Claude Code 中输入/auth0-flutter-web确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/api.mdintegration.mdsetup.mdtests/benchmark-config.jsonevals.jsongraders.jsongraders.tspackage.jsonrun-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 移动端(iOS/Android):请使用 auth0-flutter-native——移动端使用不同的平台接口,并通过系统浏览器进行 Web Auth
- React SPA:请使用 auth0-react
- 原生 iOS(Swift):请使用 auth0-swift
- 原生 Android(Kotlin/Java):请使用 auth0-android
- Angular SPA:请使用 auth0-angular
- Vue SPA:请使用 auth0-vue
- Node.js/Express 服务:请使用 auth0-express
前置条件
快速开始工作流
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 指令:
- 找到 Web 入口文件:
web/index.html- 在
<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 指令:
- 确定应用的 Web URL。本地开发时,默认值为
http://localhost:3000(使用--web-port 3000运行时)。- 通过
AskUserQuestion询问用户:"你将在本地使用哪个端口运行 Flutter Web 应用?(默认:3000)"- 使用 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) |