Skip to main content

Use AI to integrate Auth0

If you use an AI coding assistant like Claude Code, Cursor, or GitHub Copilot, you can add Auth0 authentication automatically in minutes using agent skills.Install:
npx skills add auth0/agent-skills --skill auth0-quickstart --skill auth0-flutter-web
Then ask your AI assistant:
Add Auth0 authentication to my Flutter web app
Your AI assistant will automatically create your Auth0 application, fetch credentials, add the auth0_flutter SDK dependency, configure web/index.html, set up callback URLs, and implement login/logout flows. Full agent skills documentation →
Prerequisites: Before you begin, ensure you have the following installed:Verify installation: flutter --versionIf you don’t have a Flutter web app, create one: flutter create --platforms=web my_app

Get Started

This quickstart demonstrates how to add Auth0 authentication to a Flutter Web application. You’ll build a secure single-page app with login, logout, and user profile features using the Auth0 Flutter SDK.
1

Create a new project

Create a new Flutter Web application for this quickstart:
flutter create --platforms=web auth0_flutter_web
Open the project:
cd auth0_flutter_web
2

Install the Auth0 Flutter SDK

Add the Auth0 Flutter SDK to your project using the Flutter CLI:
flutter pub add auth0_flutter
The SDK requires the Auth0 SPA JS library to be loaded in your web application. Add the following <script> tag to your web/index.html file, before the closing </body> tag:
web/index.html
<!DOCTYPE html>
<html>
<head>
  <!-- ... existing head content ... -->
</head>
<body>
  <!-- ... existing body content ... -->

  <!-- Add this before closing body tag -->
  <script src="https://cdn.auth0.com/js/auth0-spa-js/2.9/auth0-spa-js.production.js" defer></script>
</body>
</html>
The Auth0 SPA JS script is required for the Flutter Web SDK to function. Without it, authentication will not work.
3

Setup your Auth0 App

Next, you need to create a new application on your Auth0 tenant.You can choose to do this automatically by running a CLI command or manually via the Dashboard:
Run the following shell command in your project’s root directory to create an Auth0 application:macOS / Linux:
AUTH0_APP_NAME="My Flutter Web App" && \
auth0 apps create -n "${AUTH0_APP_NAME}" -t spa \
  --callbacks http://localhost:3000 \
  --logout-urls http://localhost:3000 \
  --origins http://localhost:3000 \
  --json
Windows (PowerShell):
$appName = "My Flutter Web App"
auth0 apps create -n $appName -t spa `
  --callbacks http://localhost:3000 `
  --logout-urls http://localhost:3000 `
  --origins http://localhost:3000 `
  --json
Copy the domain and client_id values from the output. You’ll use these in the next step.
If you haven’t installed the Auth0 CLI yet, run:
brew tap auth0/auth0-cli && brew install auth0
Then authenticate with auth0 login.
4

Configure the SDK

Create an instance of the Auth0Web class with your Auth0 domain and client ID values.Create a new file lib/auth0_service.dart:
lib/auth0_service.dart
import 'package:auth0_flutter/auth0_flutter_web.dart';

class Auth0Service {
  static final Auth0Service _instance = Auth0Service._internal();
  late final Auth0Web auth0Web;

  factory Auth0Service() {
    return _instance;
  }

  Auth0Service._internal() {
    auth0Web = Auth0Web(
      'YOUR_AUTH0_DOMAIN',        // Replace with your Auth0 domain
      'YOUR_AUTH0_CLIENT_ID',     // Replace with your Client ID
      cacheLocation: CacheLocation.localStorage, // Persist sessions
    );
  }
}
Replace YOUR_AUTH0_DOMAIN with your Auth0 tenant domain (e.g., dev-abc123.us.auth0.com) and YOUR_AUTH0_CLIENT_ID with your application’s Client ID from the dashboard.
Setting cacheLocation: CacheLocation.localStorage enables persistent sessions across page reloads.
5

Create the main view

Replace the contents of lib/main.dart with the following code:
lib/main.dart
import 'package:flutter/material.dart';
import 'package:auth0_flutter/auth0_flutter_web.dart';
import 'auth0_service.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Auth0 Flutter Web',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MainView(),
    );
  }
}

class MainView extends StatefulWidget {
  const MainView({super.key});

  @override
  State<MainView> createState() => _MainViewState();
}

class _MainViewState extends State<MainView> {
  final auth0Service = Auth0Service();
  Credentials? _credentials;
  bool _isLoading = true;

  @override
  void initState() {
    super.initState();
    _handleAuthCallback();
  }

  Future<void> _handleAuthCallback() async {
    try {
      final credentials = await auth0Service.auth0Web.onLoad();
      setState(() {
        _credentials = credentials;
        _isLoading = false;
      });
    } catch (e) {
      print('Error handling auth callback: $e');
      setState(() {
        _isLoading = false;
      });
    }
  }

  Future<void> _login() async {
    await auth0Service.auth0Web.loginWithRedirect(
      redirectUrl: 'http://localhost:3000',
    );
  }

  Future<void> _logout() async {
    await auth0Service.auth0Web.logout(
      returnToUrl: 'http://localhost:3000',
    );
  }

  @override
  Widget build(BuildContext context) {
    if (_isLoading) {
      return const Scaffold(
        body: Center(
          child: CircularProgressIndicator(),
        ),
      );
    }

    return Scaffold(
      body: Container(
        decoration: const BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
            colors: [Color(0xFF667eea), Color(0xFF764ba2)],
          ),
        ),
        child: Center(
          child: Card(
            elevation: 8,
            margin: const EdgeInsets.all(24),
            child: Container(
              constraints: const BoxConstraints(maxWidth: 500),
              padding: const EdgeInsets.all(48),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  const Icon(
                    Icons.security,
                    size: 64,
                    color: Color(0xFF667eea),
                  ),
                  const SizedBox(height: 24),
                  Text(
                    'Auth0 Flutter Web',
                    style: Theme.of(context).textTheme.headlineMedium?.copyWith(
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 16),
                  Container(
                    padding: const EdgeInsets.symmetric(
                      horizontal: 16,
                      vertical: 12,
                    ),
                    decoration: BoxDecoration(
                      color: _credentials != null
                          ? Colors.green.shade50
                          : Colors.red.shade50,
                      borderRadius: BorderRadius.circular(8),
                    ),
                    child: Row(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        Icon(
                          _credentials != null ? Icons.check_circle : Icons.cancel,
                          color: _credentials != null ? Colors.green : Colors.red,
                        ),
                        const SizedBox(width: 8),
                        Text(
                          _credentials != null
                              ? 'You are logged in'
                              : 'You are logged out',
                          style: TextStyle(
                            fontWeight: FontWeight.w600,
                            color: _credentials != null
                                ? Colors.green.shade900
                                : Colors.red.shade900,
                          ),
                        ),
                      ],
                    ),
                  ),
                  const SizedBox(height: 32),
                  if (_credentials == null)
                    ElevatedButton.icon(
                      onPressed: _login,
                      icon: const Icon(Icons.login),
                      label: const Text('Log In'),
                      style: ElevatedButton.styleFrom(
                        padding: const EdgeInsets.symmetric(
                          horizontal: 32,
                          vertical: 16,
                        ),
                        backgroundColor: const Color(0xFF667eea),
                        foregroundColor: Colors.white,
                      ),
                    )
                  else
                    Column(
                      children: [
                        if (_credentials!.user.pictureUrl != null)
                          CircleAvatar(
                            radius: 50,
                            backgroundImage: NetworkImage(
                              _credentials!.user.pictureUrl!.toString(),
                            ),
                          ),
                        const SizedBox(height: 16),
                        Text(
                          _credentials!.user.name ?? 'User',
                          style: Theme.of(context).textTheme.headlineSmall,
                        ),
                        const SizedBox(height: 8),
                        Text(
                          _credentials!.user.email ?? '',
                          style: Theme.of(context).textTheme.bodyMedium?.copyWith(
                            color: Colors.grey.shade600,
                          ),
                        ),
                        const SizedBox(height: 24),
                        Row(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children: [
                            ElevatedButton.icon(
                              onPressed: () {
                                Navigator.push(
                                  context,
                                  MaterialPageRoute(
                                    builder: (context) => ProfileView(
                                      credentials: _credentials!,
                                    ),
                                  ),
                                );
                              },
                              icon: const Icon(Icons.person),
                              label: const Text('View Profile'),
                              style: ElevatedButton.styleFrom(
                                padding: const EdgeInsets.symmetric(
                                  horizontal: 24,
                                  vertical: 12,
                                ),
                              ),
                            ),
                            const SizedBox(width: 16),
                            ElevatedButton.icon(
                              onPressed: _logout,
                              icon: const Icon(Icons.logout),
                              label: const Text('Log Out'),
                              style: ElevatedButton.styleFrom(
                                padding: const EdgeInsets.symmetric(
                                  horizontal: 24,
                                  vertical: 12,
                                ),
                                backgroundColor: Colors.red,
                                foregroundColor: Colors.white,
                              ),
                            ),
                          ],
                        ),
                      ],
                    ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class ProfileView extends StatelessWidget {
  final Credentials credentials;

  const ProfileView({super.key, required this.credentials});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('User Profile'),
        backgroundColor: const Color(0xFF667eea),
        foregroundColor: Colors.white,
      ),
      body: Container(
        decoration: const BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
            colors: [Color(0xFF667eea), Color(0xFF764ba2)],
          ),
        ),
        child: Center(
          child: Card(
            elevation: 8,
            margin: const EdgeInsets.all(24),
            child: Container(
              constraints: const BoxConstraints(maxWidth: 600),
              padding: const EdgeInsets.all(48),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Center(
                    child: Column(
                      children: [
                        if (credentials.user.pictureUrl != null)
                          CircleAvatar(
                            radius: 60,
                            backgroundImage: NetworkImage(
                              credentials.user.pictureUrl!.toString(),
                            ),
                          ),
                        const SizedBox(height: 16),
                        Text(
                          credentials.user.name ?? 'User',
                          style: Theme.of(context).textTheme.headlineMedium,
                        ),
                      ],
                    ),
                  ),
                  const SizedBox(height: 32),
                  const Text(
                    'Profile Information',
                    style: TextStyle(
                      fontSize: 20,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const Divider(height: 24),
                  _buildInfoRow('Email', credentials.user.email ?? 'N/A'),
                  _buildInfoRow('Name', credentials.user.name ?? 'N/A'),
                  _buildInfoRow('Nickname', credentials.user.nickname ?? 'N/A'),
                  _buildInfoRow('User ID', credentials.user.sub),
                  const SizedBox(height: 24),
                  const Text(
                    'Raw User Object',
                    style: TextStyle(
                      fontSize: 16,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 12),
                  Container(
                    padding: const EdgeInsets.all(16),
                    decoration: BoxDecoration(
                      color: Colors.grey.shade100,
                      borderRadius: BorderRadius.circular(8),
                    ),
                    child: SingleChildScrollView(
                      scrollDirection: Axis.horizontal,
                      child: SelectableText(
                        credentials.user.toString(),
                        style: const TextStyle(
                          fontFamily: 'monospace',
                          fontSize: 12,
                        ),
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }

  Widget _buildInfoRow(String label, String value) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          SizedBox(
            width: 120,
            child: Text(
              '$label:',
              style: const TextStyle(
                fontWeight: FontWeight.w600,
                color: Colors.grey,
              ),
            ),
          ),
          Expanded(
            child: SelectableText(
              value,
              style: const TextStyle(fontWeight: FontWeight.w500),
            ),
          ),
        ],
      ),
    );
  }
}
Key points:
  • onLoad() is called in initState() to handle the authentication callback
  • loginWithRedirect() redirects users to Auth0’s Universal Login page
  • logout() clears the session and redirects back to your app
  • User profile information is accessed via credentials.user
6

Run your app

Run your Flutter Web application on port 3000:
flutter run -d chrome --web-port 3000
Flutter 3.24.0 and above supports WASM compilation for improved performance:
flutter run -d chrome --web-port 3000 --wasm
CheckpointYou should now have a fully functional Auth0 login page running on http://localhost:3000. When you:
  1. Click “Log In” - you’re redirected to Auth0’s Universal Login page
  2. Complete authentication - you’re redirected back to your app
  3. Click “View Profile” - you see your user information
  4. Click “Log Out” - you’re logged out of both your app and Auth0

Advanced Usage

Configure the SDK to request an access token for calling protected APIs:
lib/auth0_service.dart
Auth0Service._internal() {
  auth0Web = Auth0Web(
    'YOUR_AUTH0_DOMAIN',
    'YOUR_AUTH0_CLIENT_ID',
    cacheLocation: CacheLocation.localStorage,
  );
}

Future<String?> getAccessToken({String? audience}) async {
  try {
    final token = await auth0Web.getTokenSilently(
      audience: audience ?? 'YOUR_API_IDENTIFIER',
    );
    return token;
  } catch (e) {
    print('Error getting access token: $e');
    return null;
  }
}
Use the access token to call your API:
Future<void> callProtectedApi() async {
  final accessToken = await Auth0Service().getAccessToken();

  if (accessToken != null) {
    final response = await http.get(
      Uri.parse('https://your-api.example.com/protected'),
      headers: {
        'Authorization': 'Bearer $accessToken',
      },
    );

    print('API Response: ${response.body}');
  }
}
Pass additional parameters to the login flow:
Future<void> _loginWithGoogle() async {
  await auth0Service.auth0Web.loginWithRedirect(
    redirectUrl: 'http://localhost:3000',
    authorizationParams: AuthorizationParams(
      connection: 'google-oauth2', // Force Google login
      screen_hint: 'signup',       // Show signup screen
    ),
  );
}

Future<void> _loginWithCustomScope() async {
  await auth0Service.auth0Web.loginWithRedirect(
    redirectUrl: 'http://localhost:3000',
    authorizationParams: AuthorizationParams(
      scope: 'openid profile email read:messages',
      audience: 'https://your-api.example.com',
    ),
  );
}
Implement proper error handling for authentication failures:
Future<void> _handleAuthCallback() async {
  try {
    final credentials = await auth0Service.auth0Web.onLoad();
    setState(() {
      _credentials = credentials;
      _isLoading = false;
    });
  } on Auth0Exception catch (e) {
    // Handle Auth0-specific errors
    print('Auth0 Error: ${e.message}');
    _showErrorDialog(e.message);
    setState(() {
      _isLoading = false;
    });
  } catch (e) {
    // Handle other errors
    print('Error: $e');
    _showErrorDialog('An unexpected error occurred');
    setState(() {
      _isLoading = false;
    });
  }
}

void _showErrorDialog(String message) {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('Authentication Error'),
      content: Text(message),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('OK'),
        ),
      ],
    ),
  );
}
Check if the user is already authenticated without showing the login page:
Future<bool> checkAuthentication() async {
  try {
    final credentials = await auth0Service.auth0Web.onLoad();
    return credentials != null;
  } catch (e) {
    return false;
  }
}

Future<void> silentLogin() async {
  try {
    // Attempt to get a token silently
    final token = await auth0Service.auth0Web.getTokenSilently();
    if (token != null) {
      // User is authenticated
      print('User is authenticated');
    }
  } catch (e) {
    // User needs to log in
    print('User needs to log in');
  }
}

Troubleshooting

”Callback URL mismatch” error

Problem: The callback URL doesn’t match what’s configured in Auth0.Solution: Ensure the callback URL in your code matches exactly what’s in the Auth0 Dashboard:
  1. Go to Auth0 Dashboard → Applications → Your App → Settings
  2. Verify Allowed Callback URLs includes http://localhost:3000
  3. The URL must match exactly (no trailing slashes unless you include them in code)

Authentication not working

Problem: Login button does nothing or authentication fails.Solution: Verify the Auth0 SPA JS script is loaded in web/index.html:
<script src="https://cdn.auth0.com/js/auth0-spa-js/2.9/auth0-spa-js.production.js" defer></script>
This script must be present before the closing </body> tag.

User logged out after page refresh

Problem: User session doesn’t persist across page reloads.Solutions:
  1. Ensure Allowed Web Origins includes http://localhost:3000 in Auth0 Dashboard
  2. Use cacheLocation: CacheLocation.localStorage when creating Auth0Web instance
  3. Verify onLoad() is called in your widget’s initState()

”Invalid state” error

Problem: State mismatch during authentication callback.Solutions:
  1. Clear browser cache and local storage
  2. Ensure you’re not opening multiple tabs during login
  3. Verify your callback URL is correct

CORS errors in browser console

Problem: Cross-Origin Resource Sharing errors.Solution:
  1. Add http://localhost:3000 to Allowed Web Origins in Auth0 Dashboard
  2. Ensure you’re running on port 3000 (matching your configuration)

Next Steps

Now that you have authentication working, consider exploring:

Resources