plugins/auth0/skills/auth0-flutter-web/SKILL.md
Use when adding Auth0 authentication to a Flutter web application — integrates the auth0_flutter SDK (web platform) for browser-based authentication using redirect login, popup login, and credential caching.
npx skillsauth add auth0/agent-skills auth0-flutter-webInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
auth0_flutter is the official Auth0 SDK for Flutter applications. On the web platform, it wraps the Auth0 SPA JS SDK to provide browser-based authentication via redirect or popup flows with built-in credential caching and automatic token renewal.
Agent instruction: The current SDK version is
2.1.0. Use this version in all dependency lines below. If you need to verify, runflutter pub add auth0_flutterwhich will resolve the latest compatible version automatically.
Agent instruction: Follow these steps in order. If you encounter an error at any step, attempt to fix it up to 5 times before calling
AskUserQuestionto ask the user for guidance. Always search existing code first — if there are existing login/logout handlers, hook into them rather than creating new ones.
Agent instruction: Check the project directory for
pubspec.yaml. If present, add the dependency. If not found, this is not a Flutter project — ask the user.Run in the project root:
flutter pub add auth0_flutterVerify the dependency was added to
pubspec.yaml:dependencies: auth0_flutter: ^2.1.0
Note: For Single Page Applications, the Auth0 Domain and Client ID are public configuration (not secrets). A SPA uses PKCE with no client secret, and these values ship in the browser bundle. Pass them directly to
Auth0Web(domain, clientId)— there is no need to store them in environment variables or hide them.Agent instruction:
- If Auth0 credentials (domain AND client ID) are already in the user's prompt: Proceed to Step 3 and use those values directly in the
Auth0Web(...)constructor.- If no credentials are provided: Create the application with the Auth0 CLI — do NOT ask the user to create or configure an Auth0 application manually in the Dashboard. Always use the CLI path.
Follow Setup Guide — Auth0 Configuration for the pre-flight checks and the
auth0 apps createcommand.
Agent instruction:
- Locate the web entry point:
web/index.html- Add the Auth0 SPA JS script tag inside
<head>:<script src="https://cdn.auth0.com/js/auth0-spa-js/2.1/auth0-spa-js.production.js" defer></script>If
web/index.htmldoes not exist, the user may need to runflutter create . --platforms=webto add web support.
Agent instruction:
- Determine the app's web URL. For local development, default is
http://localhost:3000(when running with--web-port 3000).- Ask the user via
AskUserQuestion: "What port will you run your Flutter web app on locally? (default: 3000)"- Register the callback URLs using the Auth0 CLI (substitute real values for
CLIENT_ID,APP_URL):auth0 apps update CLIENT_ID \ --callbacks "APP_URL" \ --logout-urls "APP_URL" \ --web-origins "APP_URL" \ --no-inputFor production, also add the production URL to each list.
Agent instruction: Search the project for the main app entry point (
main.dart). Determine the state management approach:
- Look for
provider,riverpod,bloc,GetX, ormobximports- If none found, use basic
StatefulWidgetwithsetStateThen follow only the matching path below. If ambiguous, ask via
AskUserQuestion: "Which state management approach does your Flutter app use — Provider, Riverpod, Bloc, or basic setState?"
Agent instruction: Create an
AuthServiceclass, then wire it into the app's root widget. Search for theMaterialApporCupertinoAppwidget and update accordingly.
// 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 ?? ''),
],
),
),
);
}
}
Agent instruction: If the project uses
provider, createAuthServiceas aChangeNotifierand inject it viaChangeNotifierProviderat the app root.
// 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(),
),
);
}
For complete patterns with Riverpod, Bloc, and advanced scenarios, see Integration Patterns.
Agent instruction: Run a build to verify the integration compiles without errors:
flutter build webThen run the app locally to test:
flutter run -d chrome --web-port 3000If the build fails, review error messages and fix up to 5 times before asking the user.
| Mistake | Fix |
|---------|-----|
| Auth0 app type not set to Single Page Application | In Auth0 Dashboard, select "Single Page Application" when creating the application |
| Missing Auth0 SPA JS script in web/index.html | Add <script src="https://cdn.auth0.com/js/auth0-spa-js/2.1/auth0-spa-js.production.js" defer></script> to <head> |
| Not calling onLoad() on app startup | Always call onLoad() in initState() or equivalent to restore sessions after redirect |
| Missing Allowed Web Origins in Auth0 Dashboard | Add your app URL (e.g., http://localhost:3000) to Allowed Web Origins — required for silent token renewal |
| Using auth0_flutter mobile API on web | Import both package:auth0_flutter/auth0_flutter.dart (for types like Credentials, UserProfile) AND package:auth0_flutter/auth0_flutter_web.dart (for Auth0Web class) |
| Missing base import causes type errors | Credentials and UserProfile are exported from auth0_flutter.dart, not auth0_flutter_web.dart — you need both imports |
| Callback URL mismatch | Ensure Allowed Callback URLs matches the exact URL where your app runs (including port) |
| Not adding --web-port when running locally | Use flutter run -d chrome --web-port 3000 to ensure consistent port matching callback URLs |
| Popup blocked by browser | loginWithPopup() must be called from a direct user interaction (button click); cannot be called from initState() |
| Missing offline_access scope | Add 'offline_access' to scopes set to enable refresh token rotation for silent renewal |
| CORS errors on token endpoint | Ensure Allowed Web Origins is configured in Auth0 Dashboard (not just Callback URLs) |
development
Use when adding login, logout, and user profile to a Laravel web application using session-based authentication - integrates auth0/login (laravel-auth0) for guard-based auth with auto-registered routes.
tools
Use when securing Laravel API endpoints with JWT Bearer token validation, scope/permission checks, or stateless auth - integrates auth0/login (laravel-auth0) with the AuthorizationGuard for REST APIs receiving access tokens from SPAs, mobile apps, or other clients. Triggers on: Laravel API auth, auth0.authorizer, AuthorizationGuard, Laravel JWT, stateless Bearer.
development
Use when adding Auth0 authentication to a Flutter mobile application (iOS/Android) — integrates the auth0_flutter SDK (native platform) for Web Auth login/logout via the system browser, with secure credential storage and biometric protection through the CredentialsManager.
tools
Use when adding Auth0 authentication to Windows Forms (WinForms) desktop applications - integrates Auth0.OidcClient.WinForms NuGet package for native login, logout, token refresh, and user profile. Trigger on WinForms authentication, add login to WinForms, Auth0 WinForms, .NET Windows Forms auth, Windows desktop auth