auth0-flutter-web

Par auth0 · agent-skills

À utiliser lors de l'ajout de l'authentification Auth0 à une application Flutter web — intègre le SDK auth0_flutter (plateforme web) pour l'authentification en navigateur via la connexion par redirection, la connexion par popup et la mise en cache des identifiants.

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

Intégration Auth0 Flutter Web

auth0_flutter est le SDK officiel Auth0 pour les applications Flutter. Sur la plateforme web, il enveloppe le SDK Auth0 SPA JS pour fournir une authentification basée sur le navigateur via des flux de redirection ou popup avec mise en cache des identifiants intégrée et renouvellement automatique des tokens.

Agent instruction: La version actuelle du SDK est 2.1.0. Utilisez cette version dans toutes les lignes de dépendance ci-dessous. Si vous avez besoin de vérifier, exécutez flutter pub add auth0_flutter qui résoudra automatiquement la dernière version compatible.

Quand NE PAS utiliser

Prérequis

Flux Quick Start

Agent instruction: Suivez ces étapes dans l'ordre. Si vous rencontrez une erreur à une étape, tentez de la corriger jusqu'à 5 fois avant d'appeler AskUserQuestion pour demander des conseils à l'utilisateur. Cherchez toujours le code existant en premier — s'il y a déjà des gestionnaires de connexion/déconnexion, branchez-vous dessus plutôt que d'en créer de nouveaux.

Étape 1 — Installer le SDK

Agent instruction: Vérifiez le répertoire du projet pour pubspec.yaml. S'il est présent, ajoutez la dépendance. S'il n'est pas trouvé, ce n'est pas un projet Flutter — demandez à l'utilisateur.

Exécutez dans la racine du projet :

flutter pub add auth0_flutter

Vérifiez que la dépendance a été ajoutée à pubspec.yaml :

dependencies:
  auth0_flutter: ^2.1.0

Étape 2 — Configurer Auth0

Note: Pour les Single Page Applications, le domaine Auth0 et l'ID client sont une configuration publique (pas des secrets). Un SPA utilise PKCE sans secret client, et ces valeurs sont expédiées dans le bundle du navigateur. Passez-les directement au constructeur Auth0Web(domain, clientId) — inutile de les stocker dans des variables d'environnement ou de les masquer.

Agent instruction:

  • Si les identifiants Auth0 (domaine ET ID client) sont déjà dans le prompt de l'utilisateur: Procédez à l'étape 3 et utilisez ces valeurs directement dans le constructeur Auth0Web(...).
  • Si aucun identifiant n'est fourni: Créez l'application avec Auth0 CLI — NE DEMANDEZ PAS à l'utilisateur de créer ou configurer manuellement une application Auth0 dans le Dashboard. Utilisez toujours le chemin CLI.

Suivez Guide de configuration — Configuration Auth0 pour les vérifications préalables et la commande auth0 apps create.

Étape 3 — Ajouter Auth0 SPA JS à index.html

Agent instruction:

  1. Localisez le point d'entrée web : web/index.html
  2. Ajoutez la balise de script Auth0 SPA JS à l'intérieur de <head> :
<script src="https://cdn.auth0.com/js/auth0-spa-js/2.1/auth0-spa-js.production.js" defer></script>

Si web/index.html n'existe pas, l'utilisateur devra peut-être exécuter flutter create . --platforms=web pour ajouter le support web.

Étape 4 — Configurer les URL de callback

Agent instruction:

  1. Déterminez l'URL web de l'application. Pour le développement local, le défaut est http://localhost:3000 (lors de l'exécution avec --web-port 3000).
  2. Posez à l'utilisateur via AskUserQuestion : "Sur quel port exécuterez-vous votre application Flutter web localement ? (défaut : 3000)"
  3. Enregistrez les URL de callback en utilisant Auth0 CLI (remplacez les valeurs réelles pour CLIENT_ID, APP_URL) :
auth0 apps update CLIENT_ID \
  --callbacks "APP_URL" \
  --logout-urls "APP_URL" \
  --web-origins "APP_URL" \
  --no-input

Pour la production, ajoutez également l'URL de production à chaque liste.

Étape 5 — Implémenter l'authentification

Agent instruction: Cherchez le point d'entrée principal de l'application (main.dart). Déterminez l'approche de gestion d'état :

  • Cherchez les imports provider, riverpod, bloc, GetX, ou mobx
  • Si aucun n'est trouvé, utilisez StatefulWidget basique avec setState

Puis suivez uniquement le chemin correspondant ci-dessous. Si c'est ambigu, demandez via AskUserQuestion : "Quelle approche de gestion d'état votre application Flutter utilise-t-elle — Provider, Riverpod, Bloc, ou setState basique ?"

StatefulWidget basique (Par défaut)

Agent instruction: Créez une classe AuthService, puis intégrez-la dans le widget racine de l'application. Cherchez le widget MaterialApp ou CupertinoApp et mettez à jour en conséquence.

// 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;

  /// Appelez au démarrage de l'application pour restaurer la session depuis le cache
  Future<void> onLoad() async {
    _credentials = await _auth0.onLoad();
  }

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

  /// Ouvrir la connexion Auth0 dans une fenêtre popup
  Future<void> loginWithPopup() async {
    _credentials = await _auth0.loginWithPopup(
      scopes: {'openid', 'profile', 'email', 'offline_access'},
    );
  }

  /// Obtenir les identifiants en cache (renouvelle automatiquement s'ils ont expiré)
  Future<Credentials> getCredentials() async {
    final creds = await _auth0.credentials();
    _credentials = creds;
    return creds;
  }

  /// Vérifier si des identifiants valides existent
  Future<bool> hasValidCredentials() async {
    return await _auth0.hasValidCredentials();
  }

  /// Se déconnecter et rediriger vers l'application
  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 (Gestion d'état)

Agent instruction: Si le projet utilise provider, créez AuthService en tant que ChangeNotifier et injectez-le via ChangeNotifierProvider à la racine de l'application.

// 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 — envelopper avec 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(),
    ),
  );
}

Pour les modèles complets avec Riverpod, Bloc, et les scénarios avancés, voir Modèles d'intégration.

Étape 6 — Vérifier la compilation

Agent instruction: Lancez une compilation pour vérifier que l'intégration se compile sans erreurs :

flutter build web

Puis lancez l'application localement pour tester :

flutter run -d chrome --web-port 3000

Si la compilation échoue, examinez les messages d'erreur et corrigez jusqu'à 5 fois avant de demander à l'utilisateur.

Documentation détaillée

  • Guide de configuration — Création d'application Auth0 via Auth0 CLI, configuration web/index.html, enregistrement des URL de callback
  • Modèles d'intégration — Connexion par redirection, connexion par popup, gestion des identifiants, modèles de gestion d'état, support des organisations, gestion des erreurs
  • Référence API et tests — Référence API complète, options de configuration, référence des claims, liste de vérification des tests, dépannage

Erreurs courantes

Erreur Correction
Type d'application Auth0 non défini sur Single Page Application Dans Auth0 Dashboard, sélectionnez « Single Page Application » lors de la création de l'application
Script Auth0 SPA JS manquant dans web/index.html Ajoutez <script src="https://cdn.auth0.com/js/auth0-spa-js/2.1/auth0-spa-js.production.js" defer></script> à <head>
onLoad() non appelé au démarrage de l'application Appelez toujours onLoad() dans initState() ou équivalent pour restaurer les sessions après redirection
Allowed Web Origins manquant dans Auth0 Dashboard Ajoutez l'URL de votre application (p. ex. http://localhost:3000) à Allowed Web Origins — requis pour le renouvellement silencieux des tokens
Utilisation de l'API mobile auth0_flutter sur le web Importez à la fois package:auth0_flutter/auth0_flutter.dart (pour les types comme Credentials, UserProfile) ET package:auth0_flutter/auth0_flutter_web.dart (pour la classe Auth0Web)
L'import de base manquant cause des erreurs de type Credentials et UserProfile sont exportés de auth0_flutter.dart, pas de auth0_flutter_web.dart — vous avez besoin des deux imports
Mismatch d'URL de callback Assurez-vous que Allowed Callback URLs correspond exactement à l'URL où votre application s'exécute (y compris le port)
--web-port non ajouté lors de l'exécution locale Utilisez flutter run -d chrome --web-port 3000 pour assurer une correspondance cohérente des ports avec les URL de callback
Popup bloquée par le navigateur loginWithPopup() doit être appelée à partir d'une interaction utilisateur directe (clic de bouton) ; ne peut pas être appelée depuis initState()
Scope offline_access manquant Ajoutez 'offline_access' à l'ensemble des scopes pour activer la rotation des tokens de rafraîchissement pour le renouvellement silencieux
Erreurs CORS sur l'endpoint de token Assurez-vous que Allowed Web Origins est configuré dans Auth0 Dashboard (pas seulement Callback URLs)

Références

Skills similaires