Skip to content

Latest commit

 

History

History
1777 lines (1315 loc) · 76.4 KB

File metadata and controls

1777 lines (1315 loc) · 76.4 KB

Examples

📱 Web Authentication


Log out

Logging the user out involves clearing the Universal Login session cookie and then deleting the user's credentials from your app.

Call the logout() method in the onPressed callback of your Logout button. Once the session cookie has been cleared, auth0_flutter will automatically delete the user's credentials.

Mobile

If you're using your own credentials storage, make sure to delete the credentials afterward.

// Use a Universal Link logout URL on iOS 17.4+ / macOS 14.4+
// useHTTPS is ignored on Android
await auth0.webAuthentication().logout(useHTTPS: true);
Windows

appCustomURL is required for logout. It is the custom-scheme URL your Windows app listens on. returnTo is optional — if omitted, appCustomURL is used in the Auth0 logout URL as well.

// Option A — direct custom scheme (appCustomURL is used as returnTo too)
await auth0.windowsWebAuthentication().logout(
  appCustomURL: 'myapp://callback',
);

// Option B — intermediary HTTPS server
// Auth0 redirects to the HTTPS URL; the server redirects on to your custom scheme
await auth0.windowsWebAuthentication().logout(
  appCustomURL: 'myapp://callback',
  returnTo: 'https://your-app.example.com/logout',
);
Web
if (kIsWeb) {
  await auth0Web.logout(returnToUrl: 'http://localhost:3000');
}

💡 You need to import the 'flutter/foundation.dart' library to access the kIsWeb constant. If your app does not support other platforms, you can remove this condition.

Sign up

You can make users land directly on the Signup page instead of the Login page by specifying the 'screen_hint': 'signup' parameter. Note that this can be combined with 'prompt': 'login', which indicates whether you want to always show the authentication page or you want to skip if there's an existing session.

Parameters No existing session Existing session
No extra parameters Shows the login page Redirects to the callback URL
'screen_hint': 'signup' Shows the signup page Redirects to the callback URL
'prompt': 'login' Shows the login page Shows the login page
'prompt': 'login', 'screen_hint': 'signup' Shows the signup page Shows the signup page
Mobile
final credentials = await auth0
    .webAuthentication()
    .login(parameters: {'screen_hint': 'signup'});
Web
await auth0Web.loginWithRedirect(
    redirectUrl: 'http://localhost:3000',
    parameters: {'screen_hint': 'signup'});

⚠️ The screen_hint parameter will work with the New Universal Login Experience without any further configuration. If you are using the Classic Universal Login Experience, you need to customize the login template to look for this parameter and set the initialScreen option of the Auth0Lock constructor.

Adding an audience

Specify an audience value to obtain an access token that can be used to make authenticated requests to a backend. The audience value is the API Identifier of your Auth0 API, for example https://example.com/api.

Mobile
final credentials = await auth0
    .webAuthentication()
    .login(audience: 'YOUR_AUTH0_API_IDENTIFIER');
Web
await auth0Web.loginWithRedirect(
    redirectUrl: 'http://localhost:3000',
    audience: 'YOUR_AUTH0_API_IDENTIFIER');

Adding scopes

Specify scopes to request permission to access protected resources, like the user profile.

Mobile

The default scope values are openid, profile, email, and offline_access. Regardless of the values specified, openid is always included.

final credentials = await auth0
    .webAuthentication()
    .login(scopes: {'profile', 'email', 'offline_access', 'read:todos'});
Web

The default scope values are openid, profile, and email. Regardless of the values specified, openid is always included.

await auth0Web.loginWithRedirect(
    redirectUrl: 'http://localhost:3000',
    scopes: {'profile', 'email', 'read:todos'});

Adding custom parameters

Specify additional parameters by passing a parameters map.

Mobile
final credentials = await auth0
    .webAuthentication()
    .login(parameters: {'connection': 'github'});
Web

Custom parameters can be configured globally.

await auth0Web.onLoad(
    parameters: {'connection': 'github'});

Custom parameters can be configured when calling loginWithRedirect. Any globally configured parameter (passed to onLoad()) can be overriden when passing the same custom parameter to loginWithRedirect.

await auth0Web.loginWithRedirect(
    redirectUrl: 'http://localhost:3000',
    parameters: {'connection': 'github'});

ID token validation

auth0_flutter automatically validates the ID token obtained from Web Auth login, following the OpenID Connect specification. This ensures the contents of the ID token have not been tampered with and can be safely used.

Mobile

You can configure the ID token validation by passing an IdTokenValidationConfig instance. Check the API documentation to learn more about the available configuration options.

const config = IdTokenValidationConfig(leeway: 180);
final credentials =
    await auth0.webAuthentication().login(idTokenValidationConfig: config);
Web

You can configure issuer and leeway values through the onLoad() method.

auth0Web
    .onLoad(issuer: 'https://example.com/', leeway: 180)
    .then((final credentials) {
  // ...
});

The max_age value can be configured through loginWithRedirect() and loginWithPopup().

await auth0Web.loginWithRedirect(
    redirectUrl: 'http://localhost:3000',
    maxAge: 7200);

Using SFSafariViewController (iOS only)

auth0_flutter supports using SFSafariViewController as the browser instead of ASWebAuthenticationSession. Note that it can only be used for login, not for logout. According to its docs, SFSafariViewController must be used "to visibly present information to users":

Screenshot of SFSafariViewController's documentation

This is the case for login, but not for logout. Instead of calling logout(), you can delete the stored credentials –using the Credentials Manager's clearCredentials() method– and use 'prompt': 'login' to force the login page even if the session cookie is still present. Since the cookies stored by SFSafariViewController are scoped to your app, this should not pose an issue.

await auth0.webAuthentication().login(
      safariViewController: const SafariViewController(),
      parameters: {'prompt': 'login'}); // Ignore the cookie (if present) and show the login page

💡 SFSafariViewController does not support using a Universal Link as callback URL. See https://auth0.github.io/Auth0.swift/documentation/auth0/useragents to learn more about the differences between ASWebAuthenticationSession and SFSafariViewController.

If you choose to use SFSafariViewController, you need to perform an additional bit of setup. Unlike ASWebAuthenticationSession, SFSafariViewController will not automatically capture the callback URL when Auth0 redirects back to your app, so it is necessary to manually resume the login operation.

1. Configure a custom URL scheme

There is an Info.plist file in the ios/Runner (or macos/Runner, for macOS) directory of your app. Open it and add the following snippet inside the top-level <dict> tag. This registers your iOS/macOS bundle identifier as a custom URL scheme, so the callback URL can reach your app.

<!-- Info.plist -->

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- ... -->
    <key>CFBundleURLTypes</key>
    <array>
        <dict>
            <key>CFBundleTypeRole</key>
            <string>None</string>
            <key>CFBundleURLName</key>
            <string>auth0</string>
            <key>CFBundleURLSchemes</key>
            <array>
                <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
            </array>
        </dict>
    </array>
<!-- ... -->
</dict>
</plist>

💡 If you're opening the Info.plist file in Xcode and it is not being shown in this format, you can Right Click on Info.plist in the Xcode project navigator and then select Open As > Source Code.

2. Capture the callback URL

Using the UIKit app lifecycle
// AppDelegate.swift

override func application(_ app: UIApplication,
                 open url: URL,
                 options: [UIApplication.OpenURLOptionsKey: Any]) -> Bool {
    WebAuthentication.resume(with: url)
    return super.application(application, open: url, options: options);
}
Using the UIKit app lifecycle with Scenes
// SceneDelegate.swift

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    guard let url = URLContexts.first?.url else { return }
    WebAuthentication.resume(with: url)
}

Using Partial Custom Tabs (Android only)

On Android, auth0_flutter supports Partial Custom Tabs, which display the authentication page as a bottom sheet or side sheet instead of a full-screen browser tab. This requires Chrome 107+ (bottom sheet) or Chrome 120+ (side sheet). On older browsers, the options are ignored and the tab opens full-screen.

await auth0.webAuthentication().login(
    customTabsOptions: const CustomTabsOptions(
        initialHeight: 700,
        toolbarCornerRadius: 16,
        resizable: false,
        backgroundInteractionEnabled: true,
        allowedBrowsers: ['com.android.chrome'],
    ));

The available options are:

Option Type Description
initialHeight int? Bottom sheet height in dp. Chrome enforces a minimum of 50% of screen height.
resizable bool? Whether the user can drag to resize the bottom sheet. Defaults to true.
toolbarCornerRadius int? Top corner radius in dp (0–16). Only applies in bottom sheet mode.
initialWidth int? Side sheet width in dp. Only applies on screens wider than sideSheetBreakpoint.
sideSheetBreakpoint int? Screen width threshold (dp) to switch between bottom sheet and side sheet. Defaults to the browser's built-in value (typically 840dp).
backgroundInteractionEnabled bool? Whether the user can interact with the app behind the partial tab. Defaults to false.
allowedBrowsers List<String> Allowlist of browser packages for Custom Tabs.

You can also use customTabsOptions during logout:

await auth0.webAuthentication().logout(
    customTabsOptions: const CustomTabsOptions(
        initialHeight: 500,
        toolbarCornerRadius: 12,
    ));

Errors

Mobile

Web Auth will only throw WebAuthenticationException exceptions. Check the API documentation to learn more about the available WebAuthenticationException properties.

try {
  final credentials = await auth0.webAuthentication().login();
  // ...
} on WebAuthenticationException catch (e) {
  if (e.isRetryable) {
    // Transient error (e.g. network issue) — safe to retry
  } else {
    print(e);
  }
}

The isRetryable property indicates whether the error is transient (e.g. a network outage) and the operation can be retried.

Web

The web implementation will only throw WebException exceptions. Check the API documentation to learn more about the available WebException properties.

try {
  await auth0Web.loginWithRedirect(redirectUrl: 'http://localhost:3000');
} on WebException catch (e) {
  print(e);
}

Android: Custom schemes

Whenever possible, Auth0 recommends using Android App Links as a secure way to link directly to content within your app. Custom URL schemes can be subject to client impersonation attacks.

On Android, https is used by default as the callback URL scheme. This works best for Android API 23+ if you're using Android App Links, but in previous Android versions, this may show the intent chooser dialog prompting the user to choose either your app or the browser. You can change this behavior by using a custom unique scheme so that Android opens the link directly with your app.

  1. Update the auth0Scheme manifest placeholder on the android/build.gradle file.
  2. Update the Allowed Callback URLs in the settings page of your Auth0 application.
  3. Pass the scheme value to the webAuthentication() method.
final webAuth = auth0.webAuthentication(scheme: 'YOUR_CUSTOM_SCHEME');

// Login
final credentials = await webAuth.login();

// Logout
await webAuth.logout();

💡 Note that custom schemes can only have lowercase letters.

Go up ⤴

🪟 Windows Web Authentication

Windows uses windowsWebAuthentication() instead of webAuthentication().

appCustomURL — why it is required

On Windows, the browser cannot directly activate a desktop app the way iOS/Android handle universal links. Instead, the app registers a custom URL scheme (e.g. myapp://callback) as a protocol handler in the Windows registry. When the browser navigates to that URL, Windows launches (or brings to the front) your Flutter app and passes the URL as a command-line argument — this is the appCustomURL.

appCustomURL must always be passed. It tells the SDK which URL scheme your app is listening on so it can intercept the browser redirect.

redirectUrl (login) and returnTo (logout) — when to pass them

Scenario What to pass What happens
Simple setup (recommended) appCustomURL only Auth0 redirects straight to the custom scheme; appCustomURL is used as redirect_uri / returnTo in the Auth0 URL. Register your scheme (e.g. myapp://callback) in your dashboard.
Intermediary HTTPS server appCustomURL + redirectUrl / returnTo Auth0 redirects to your HTTPS server; the server then redirects onward to appCustomURL. Register the HTTPS URL in your dashboard. Useful when you want no custom-scheme URL visible in the browser address bar.

See the Windows configuration section in the README for the full setup guide, including the required runner changes.

Prerequisites

Before using windowsWebAuthentication(), your Windows Flutter app needs a few one-time setup steps.

1. Install vcpkg and native dependencies

The auth0_flutter Windows plugin depends on native C++ libraries managed by vcpkg. Install vcpkg and set the VCPKG_ROOT environment variable:

# Clone vcpkg (if you haven't already)
git clone https://github.com/microsoft/vcpkg.git C:\vcpkg
cd C:\vcpkg
.\bootstrap-vcpkg.bat

# Set the environment variable (persist it in System Properties > Environment Variables)
setx VCPKG_ROOT "C:\vcpkg"

The plugin's vcpkg.json manifest automatically pulls the required packages (cpprestsdk, openssl, boost-system, boost-date-time, boost-regex) at build time — no manual vcpkg install is needed.

2. Configure your app's CMakeLists.txt

Your app's top-level windows/CMakeLists.txt must enable vcpkg toolchain integration so that the plugin's native dependencies can be resolved. Add the following before the first project() call:

# windows/CMakeLists.txt

cmake_minimum_required(VERSION 3.14)

# --- vcpkg integration (required for auth0_flutter) ---
if(DEFINED ENV{VCPKG_ROOT} AND EXISTS "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake")
    set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
        CACHE STRING "Vcpkg toolchain file")
endif()

project(your_app LANGUAGES CXX)

# ... rest of your CMakeLists.txt ...

⚠️ The CMAKE_TOOLCHAIN_FILE line must appear before project(). If it appears after, CMake will have already configured the compiler and vcpkg packages will not be found, resulting in build errors like Could not find a package configuration file provided by "cpprestsdk".

3. Register the custom URL scheme (protocol handler)

For Windows to route your custom-scheme callback URLs (e.g. myapp://callback) back to your app, you must register the scheme as a protocol handler in the Windows Registry. Choose a scheme name that is unique to your application. This is typically done once when the app is installed.

💡 The scheme can be anything you choose — myapp, com.example.myapp, etc. Use the same value as appCustomURL in your Dart code and register it in the Auth0 dashboard's Allowed Callback URLs / Allowed Logout URLs.

Option A — Manual registration (development)

Create a .reg file with the following contents and double-click it to import. Replace myapp with your chosen scheme and update the executable path:

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Classes\myapp]
@="URL:myapp Protocol"
"URL Protocol"=""

[HKEY_CURRENT_USER\Software\Classes\myapp\shell]

[HKEY_CURRENT_USER\Software\Classes\myapp\shell\open]

[HKEY_CURRENT_USER\Software\Classes\myapp\shell\open\command]
@="\"C:\\Path\\To\\Your\\App\\your_app.exe\" \"%1\""

Replace C:\Path\To\Your\App\your_app.exe with the actual path to your built Flutter executable (e.g. build\windows\x64\runner\Release\your_app.exe).

💡 During development you can point this at your debug build path. Remember to update it when you move to a release/installed location.

Option B — Programmatic registration (installer / first-run)

If you use an installer (MSIX, Inno Setup, WiX, etc.), add the registry entries as part of the install step. For MSIX, declare the protocol in your Package.appxmanifest:

<Extensions>
  <uap:Extension Category="windows.protocol">
    <uap:Protocol Name="myapp">
      <uap:DisplayName>My App Callback</uap:DisplayName>
    </uap:Protocol>
  </uap:Extension>
</Extensions>

For a first-run self-registration approach, you can write the registry keys programmatically from your app's main.cpp. Replace myapp with your chosen scheme:

// Call once on first launch to register the protocol handler.
// schemeName: your custom scheme (e.g. L"myapp")
// exePath:    full path to the running executable
void RegisterProtocolHandler(const std::wstring& schemeName,
                             const std::wstring& exePath) {
    HKEY hKey;
    std::wstring keyPath = L"Software\\Classes\\" + schemeName;

    RegCreateKeyExW(HKEY_CURRENT_USER, keyPath.c_str(), 0, NULL,
                    0, KEY_WRITE, NULL, &hKey, NULL);
    std::wstring desc = L"URL:" + schemeName + L" Protocol";
    RegSetValueExW(hKey, NULL, 0, REG_SZ,
                   (const BYTE*)desc.c_str(), (DWORD)((desc.size() + 1) * sizeof(wchar_t)));
    RegSetValueExW(hKey, L"URL Protocol", 0, REG_SZ, (const BYTE*)L"", sizeof(wchar_t));
    RegCloseKey(hKey);

    std::wstring cmdKeyPath = keyPath + L"\\shell\\open\\command";
    RegCreateKeyExW(HKEY_CURRENT_USER, cmdKeyPath.c_str(), 0, NULL,
                    0, KEY_WRITE, NULL, &hKey, NULL);
    std::wstring cmd = L"\"" + exePath + L"\" \"%1\"";
    RegSetValueExW(hKey, NULL, 0, REG_SZ,
                   (const BYTE*)cmd.c_str(), (DWORD)((cmd.size() + 1) * sizeof(wchar_t)));
    RegCloseKey(hKey);
}

⚠️ Without the protocol handler registration, clicking the Auth0 login link in the browser will not launch your app and authentication will time out with USER_CANCELLED.

4. Update the runner (main.cpp)

Your app's windows/runner/main.cpp must be updated to handle single-instance enforcement, capture the callback URI from argv[1], and forward it to the plugin via the PLUGIN_STARTUP_URL environment variable. Copy the reference implementation from the example runner and adapt it to your app. Update the callback prefix constant to match your chosen scheme (e.g. L"myapp://callback"). The key pieces are:

  1. Single-instance mutex — prevents a second app instance when the OS launches your app for the protocol callback; instead, the URI is forwarded to the running instance.
  2. Named pipe server — the running instance listens on a named pipe for URIs forwarded by the second launch.
  3. Startup URI capture — on first launch, argv[1] (the callback URI) is written to PLUGIN_STARTUP_URL before Flutter initializes.

💡 See the Windows configuration section in the README for a detailed walkthrough of each piece.

Login

// Simple setup — appCustomURL only (recommended for most apps)
// Register your custom scheme (e.g. 'myapp://callback') in Allowed Callback URLs
// in the Auth0 dashboard.
final credentials = await auth0.windowsWebAuthentication().login(
  appCustomURL: 'myapp://callback',
);

// Intermediary HTTPS server
// Register 'https://your-app.example.com/callback' in Allowed Callback URLs.
// Auth0 redirects to the HTTPS URL; your server redirects onward to
// myapp://callback?code=...&state=... to activate the app.
final credentials = await auth0.windowsWebAuthentication().login(
  appCustomURL: 'myapp://callback',
  redirectUrl: 'https://your-app.example.com/callback',
);

// Access token -> credentials.accessToken
// User profile -> credentials.user

⚠️ Credentials are not automatically stored on Windows. Store and manage the returned credentials object yourself (e.g., using shared_preferences or secure storage).

Logout

// Simple setup — appCustomURL only (recommended for most apps)
// Register your custom scheme (e.g. 'myapp://callback') in Allowed Logout URLs
// in the Auth0 dashboard.
await auth0.windowsWebAuthentication().logout(
  appCustomURL: 'myapp://callback',
);

// Intermediary HTTPS server
// Register 'https://your-app.example.com/logout' in Allowed Logout URLs.
// Auth0 redirects to the HTTPS URL; your server redirects onward to
// myapp://callback to re-activate the app.
await auth0.windowsWebAuthentication().logout(
  appCustomURL: 'myapp://callback',
  returnTo: 'https://your-app.example.com/logout',
);

Go up ⤴

📱 Credentials Manager

This feature is mobile/macOS only; on web, the SPA SDK used by auth0_flutter keeps its own cache. See Handling Credentials on the Web for more details.

The Credentials Manager utility allows you to securely store and retrieve the user's credentials. The credentials will be stored encrypted in Shared Preferences on Android, and in the Keychain on iOS/macOS.

💡 If you're using Web Auth, you do not need to manually store the credentials after login and delete them after logout; auth0_flutter does it automatically.

Check for stored credentials

When the users open your app, check for valid credentials. If they exist, you can retrieve them and redirect the users to the app's main flow without any additional login steps.

final isLoggedIn = await auth0.credentialsManager.hasValidCredentials();

if (isLoggedIn) {
  // Retrieve the credentials and redirect to the main flow
} else {
  // No valid credentials exist, present the login page
}

Retrieve stored credentials

The credentials will be automatically renewed (if expired) using the refresh token. This method is thread-safe.

final credentials = await auth0.credentialsManager.credentials();

💡 You do not need to call credentialsManager.storeCredentials() afterward. The Credentials Manager automatically persists the renewed credentials.

Retrieve API credentials for a specific audience (MRRT)

If your app needs an access token for a different API than the one it logged in with, use getApiCredentials(). It exchanges the stored refresh token for an access token scoped to the requested audience using a Multi-Resource Refresh Token (MRRT). If valid API credentials for that audience are already cached, they are returned without a network call; otherwise a new token is fetched and cached for next time.

final apiCredentials = await auth0.credentialsManager.getApiCredentials(
  audience: 'https://my-api.example.com',
);

print('Access token: ${apiCredentials.accessToken}');

You can request specific scopes and pass additional options:

final apiCredentials = await auth0.credentialsManager.getApiCredentials(
  audience: 'https://my-api.example.com',
  scope: {'read:data', 'write:data'},
  minTtl: 60,
  parameters: {'key': 'value'},
  headers: {'key': 'value'},
);

To remove the cached API credentials for an audience – for example, on logout:

await auth0.credentialsManager.clearApiCredentials(
  audience: 'https://my-api.example.com',
  scope: 'read:data write:data',
);

⚠️ Prerequisites: Multi-Resource Refresh Tokens must be enabled on your tenant, and the offline_access scope must have been requested at login so that a refresh token is available for the exchange.

💡 Stored API credentials are keyed by both audience and scope on every platform, so pass the same scope to clearApiCredentials() that you used when fetching them. The native APIs do not consistently report whether a matching entry existed, so this method returns void rather than a success flag.

Retrieve user profile

Fetch the user profile associated with the stored credentials. This method returns null if no credentials are present in storage.

final userProfile = await auth0.credentialsManager.user();

if (userProfile != null) {
  print('Email: ${userProfile.email}');
}

Custom implementations

flutter_auth0 exposes a built-in, default Credentials Manager implementation through the credentialsManager property. You can pass your own implementation to the Auth0 constructor. If you're using Web Auth, this implementation will be used to store the user's credentials after login and delete them after logout.

final customCredentialsManager = CustomCredentialsManager();
final auth0 = Auth0('YOUR_AUTH0_DOMAIN', 'YOUR_AUTH0_CLIENT_ID',
    credentialsManager: customCredentialsManager);
// auth0.credentialsManager is now your CustomCredentialsManager instance

Local authentication

You can enable an additional level of user authentication before retrieving credentials using the local authentication supported by the device, for example PIN or fingerprint on Android, and Face ID or Touch ID on iOS.

To enable this, pass a LocalAuthentication instance when you create your Auth0 object.

const localAuthentication =
    LocalAuthentication(title: 'Please authenticate to continue');
final auth0 = Auth0('YOUR_AUTH0_DOMAIN', 'YOUR_AUTH0_CLIENT_ID',
    localAuthentication: localAuthentication);
final credentials = await auth0.credentialsManager.credentials();

⚠️ On Android, your app's MainActivity.kt file must extend FlutterFragmentActivity instead of FlutterActivity for biometric prompts to work.

Check the API documentation to learn more about the available LocalAuthentication properties.

⚠️ Enabling local authentication will not work if you're using a custom Credentials Manager implementation. In that case, you will need to build support for local authentication into your custom implementation.

Credentials Manager configuration

You can set platform specific configuration on the CredentialManager while initialising it. On iOS, this would mean configuring the storeKey of the Auth0.swift Credentials Manager, and the accessGroup and accessibilty of SimpleKeychain. If these are not set, the default values will be used. On Android , you can configure the sharedpreferences name used to store the credentials.

const configuration = CredentialsManagerConfiguration(
    androidConfiguration: AndroidCredentialsConfiguration("testSharedPreference"),
    iosConfiguration: IOSCredentialsConfiguration(
        storeKey: "iosStoreKey",
        accessGroup: "com.example.accessGroup",
        accessibility: Accessibility.afterFirstUnlock));

final auth0 = Auth0('YOUR_AUTH0_DOMAIN', 'YOUR_AUTH0_CLIENT_ID',
    credentialsManagerConfiguration: configuration);
final credentials = await auth0.credentialsManager.credentials();

Disable credentials storage

By default, auth0_flutter will automatically store the user's credentials after login and delete them after logout, using the built-in Credentials Manager instance. If you prefer to use your own credentials storage, you need to disable the built-in Credentials Manager.

final credentials =
    await auth0.webAuthentication(useCredentialsManager: false).login();

Errors

The Credentials Manager will only throw CredentialsManagerException exceptions. You can find more information in the details property of the exception. Check the API documentation to learn more about the available CredentialsManagerException properties.

try {
final credentials = await auth0.credentialsManager.credentials();
// ...
} on CredentialsManagerException catch (e) {
if (e.isNoCredentialsFound) {
print("No credentials stored.");
} else if (e.isTokenRenewFailed) {
print("Failed to renew tokens.");
} else {
print(e);
}
}

Retryable errors

The isRetryable property on CredentialsManagerException indicates whether the error is transient and the operation can be retried. When true, the failure is likely due to a temporary condition such as a network outage. When false, the failure is permanent (e.g. an invalid refresh token) and retrying will not help — you should log the user out instead.

try {
  final credentials = await auth0.credentialsManager.credentials();
  // ...
} on CredentialsManagerException catch (e) {
  if (e.isRetryable) {
    // Transient error (e.g. network issue) — safe to retry
    print("Temporary error, retrying...");
  } else {
    // Permanent error — log the user out
    print("Credentials cannot be renewed: ${e.message}");
  }
}

The isRetryable property is available on all exception types (CredentialsManagerException, ApiException, WebAuthenticationException) across Android and iOS/macOS. It returns true when the underlying failure is network-related, indicating the operation may succeed on retry.

Native to Web SSO

Native to Web SSO allows authenticated users in your native mobile application to seamlessly transition to your web application without requiring them to log in again. This is achieved by exchanging a refresh token for a Session Transfer Token, which can then be used to establish a session in the web application.

The Session Transfer Token is:

  • Short-lived: Expires after approximately 1 minute
  • Single-use: Can only be used once to establish a web session
  • Secure: Can be bound to the user's device through IP address or ASN

For detailed configuration and implementation guidance, see the Auth0 Native to Web SSO documentation.

Prerequisites

Before using Native to Web SSO:

  1. Enable Native to Web SSO on your Auth0 tenant - This feature requires an Enterprise plan
  2. Configure your native application
  3. Request offline_access scope during login to ensure a refresh token is issued

Get a Session Transfer Token

If you authenticated via Web Auth and stored credentials with the Credentials Manager, use ssoCredentials() — it handles token renewal automatically:

try {
  final ssoCredentials = await auth0.credentialsManager.ssoCredentials();

  print('Session Transfer Token: ${ssoCredentials.sessionTransferToken}');
  print('Token Type: ${ssoCredentials.tokenType}');
  print('Expires In: ${ssoCredentials.expiresIn} seconds');
} on CredentialsManagerException catch (e) {
  print('Failed to get SSO credentials: ${e.message}');
}

If you authenticated via the Authentication API (username/password login) and already hold a refresh token, you can exchange it directly using auth0.api.ssoExchange():

try {
  final ssoCredentials = await auth0.api.ssoExchange(refreshToken: refreshToken);

  print('Session Transfer Token: ${ssoCredentials.sessionTransferToken}');
  print('Expires In: ${ssoCredentials.expiresIn} seconds');
} on ApiException catch (e) {
  print('SSO Exchange failed: ${e.code} - ${e.message}');
}

⚠️ If you are using the Credentials Manager to store the user's credentials, prefer auth0.credentialsManager.ssoCredentials() instead. It automatically handles refresh token rotation and is thread-safe, whereas auth0.api.ssoExchange() is not.

Sending the Session Transfer Token

There are two ways to send the Session Transfer Token to your web application:

Option 1: As a Query Parameter

Pass the token as a URL parameter when opening your web application:

final ssoCredentials = await auth0.credentialsManager.ssoCredentials();

final webAppUrl = Uri.parse('https://your-web-app.com/login').replace(
  queryParameters: {
    'session_transfer_token': ssoCredentials.sessionTransferToken,
  },
);

// Open using your preferred URL launcher or WebView package
await launchUrl(webAppUrl);

Your web application should then include the session_transfer_token in the /authorize request:

// In your web application
const urlParams = new URLSearchParams(window.location.search);
const sessionTransferToken = urlParams.get('session_transfer_token');

if (sessionTransferToken) {
  const authorizeUrl =
    `https://YOUR_AUTH0_DOMAIN/authorize?` +
    `client_id=YOUR_WEB_CLIENT_ID&` +
    `redirect_uri=${encodeURIComponent('https://your-web-app.com/callback')}&` +
    `response_type=code&` +
    `scope=openid profile email&` +
    `session_transfer_token=${sessionTransferToken}`;

  window.location.href = authorizeUrl;
}

Option 2: As a Cookie (WebView only)

If your application uses a WebView to open your website, you can inject the Session Transfer Token as a cookie. The cookie will be automatically sent to Auth0's /authorize endpoint when your web application initiates authentication.

final ssoCredentials = await auth0.credentialsManager.ssoCredentials();

final cookie = 'auth0_session_transfer_token=${ssoCredentials.sessionTransferToken}; '
    'path=/; '
    'domain=YOUR_AUTH0_DOMAIN; ' // Or custom domain, if your website uses one
    'secure';

// Inject via your WebView controller (e.g. webview_flutter)
await webViewController.runJavaScript(
  "document.cookie = '$cookie';",
);

// Then navigate to your web application
await webViewController.loadRequest(
  Uri.parse('https://your-web-app.com'),
);

Important: Make sure the cookie's domain matches the Auth0 domain your website is using, regardless of the domain your mobile app uses. Otherwise, the /authorize endpoint will not receive the cookie.

  • If your website uses the default Auth0 domain (like example.us.auth0.com), set the cookie's domain to this value
  • If your website uses a custom domain, use the custom domain value instead

Cookie injection is platform-specific and may require additional WebView configuration.

Note

This feature is designed for iOS and Android only. Since the purpose is to transition a native app session into a web session, calling auth0.credentialsManager.ssoCredentials() or auth0.api.ssoExchange() from any other platform is not supported.

Go up ⤴

🌐 Handling Credentials on the Web

This section describes handling credentials for the web platform. For mobile/macOS, see Credentials Manager.

The management and storage of credentials is handled internally by the underlying Auth0 SPA SDK, including refreshing the access token when it expires. The Flutter SDK provides an API for checking whether credentials are available, and the retrieval of those credentials.

Check for valid credentials

final isLoggedIn = await auth0Web.hasValidCredentials();

if (isLoggedIn) {
  // Retrieve the credentials and redirect to the main flow
} else {
  // No valid credentials exist, present the login page
}

Retrieve stored credentials

Credentials can be retrieved on application start using onLoad():

auth0Web.onLoad().then((final credentials) {
  if (credentials != null) {
    // logged in!
  }
});

They can also be retrieved at any time using credentials():

final credentials = await auth0Web.credentials();

Go up ⤴

📱 Authentication API

This feature is mobile/macOS only; the SPA SDK used by auth0_flutter does not include an API client.

The Authentication API exposes the AuthN/AuthZ functionality of Auth0, as well as the supported identity protocols like OpenID Connect, OAuth 2.0, and SAML. We recommend using Universal Login, but if you prefer to build your own UI you can use our API endpoints to do so. However, some Auth flows (grant types) are disabled by default so you must enable them on the settings page of your Auth0 application, as explained in Update Grant Types.

To log in or sign up with a username and password, the Password grant type needs to be enabled in your app. If you set the grants via the Management API you should activate both http://auth0.com/oauth/grant-type/password-realm and Password. Otherwise, the Auth0 Dashboard will take care of activating both when enabling Password.

💡 If your Auth0 account has the Bot Detection feature enabled, your requests might be flagged for verification. Check how to handle this scenario in the Bot Detection section.

⚠️ The ID tokens obtained from Web Auth login are automatically validated by auth0_flutter, ensuring their contents have not been tampered with. This is not the case for the ID tokens obtained from the Authentication API client, including the ones received when renewing the credentials using the refresh token. You must validate any ID tokens received from the Authentication API client before using the information they contain.

Login with database connection

final credentials = await auth0.api.login(
    usernameOrEmail: 'jane.smith@example.com',
    password: 'secret-password',
    connectionOrRealm: 'Username-Password-Authentication');

// Store the credentials afterward
final didStore =
    await auth0.credentialsManager.storeCredentials(credentials);
Add an audience value

Specify an audience to obtain an access token that can be used to make authenticated requests to a backend. The audience value is the API Identifier of your Auth0 API, for example https://example.com/api.

final credentials = await auth0.api.login(
    usernameOrEmail: 'jane.smith@example.com',
    password: 'secret-password',
    connectionOrRealm: 'Username-Password-Authentication',
    audience: 'YOUR_AUTH0_API_IDENTIFIER');
Add scope values

Specify scopes to request permission to access protected resources, like the user profile. The default scope values are openid, profile, email, and offline_access. Regardless of the values specified, openid is always included.

final credentials = await auth0.api.login(
    usernameOrEmail: 'jane.smith@example.com',
    password: 'secret-password',
    connectionOrRealm: 'Username-Password-Authentication',
    scopes: {'profile', 'email', 'offline_access', 'read:todos'});

Sign up with database connection

final databaseUser = await auth0.api.signup(
    email: 'jane.smith@example.com',
    password: 'secret-password',
    connection: 'Username-Password-Authentication',
    userMetadata: {'first_name': 'Jane', 'last_name': 'Smith'});

💡 You might want to log the user in after signup. See Login with database connection above for an example.

Log in with passkeys

This feature is available on iOS 16.6+ and Android 9+ (API 28) only.

Passkeys let an existing user log in with a biometric or device PIN instead of a password, using the platform authenticator (Face ID / Touch ID on iOS, the Credential Manager on Android).

⚠️ Passkeys require additional configuration on both your Auth0 tenant and your app:

  • Set up a custom domain for your tenant. Passkeys will not work without one, since the relying-party domain must be a domain you own and can host the associated domain / Digital Asset Links file on.
  • Enable passkeys for your database connection and the Passkey grant type for your application. See Configure passkeys.
  • Configure the associated domain (iOS/macOS) and the equivalent Digital Asset Links file (Android) so the OS associates your app with the relying-party domain.

The SDK exposes two methods for passkey login — passkeyLoginChallenge and passkeyCredentialExchange — and leaves presenting the OS passkey UI to your app. The flow is:

  1. Request a login challenge from Auth0 with passkeyLoginChallenge.
  2. In your app, present the platform authenticator using that challenge and obtain a WebAuthn assertion. The SDK does not do this step — call the OS APIs directly (for example, ASAuthorizationController on iOS/macOS or Credential Manager on Android, typically over your own platform channel), then map the result into a PasskeyCredential.
  3. Exchange that credential for Auth0 tokens with passkeyCredentialExchange.
// 1. Request a login challenge from Auth0.
final challenge = await auth0.api.passkeyLoginChallenge(
    connection: 'Username-Password-Authentication');

// 2. Present the OS passkey UI in your app (not provided by the SDK) using
//    `challenge.authParamsPublicKey`, then build a PasskeyCredential from the
//    resulting WebAuthn assertion. All values are base64url-encoded.
final credential = PasskeyCredential(
    id: '<base64url credentialId>',
    rawId: '<base64url credentialId>',
    type: 'public-key',
    authenticatorAttachment: 'platform',
    response: PasskeyAuthenticatorResponse(
        clientDataJSON: '<base64url clientDataJSON>',
        authenticatorData: '<base64url authenticatorData>',
        signature: '<base64url signature>',
        userHandle: '<base64url userHandle>'));

// 3. Exchange the credential for Auth0 tokens.
final credentials = await auth0.api.passkeyCredentialExchange(
    challenge: challenge,
    credential: credential,
    connection: 'Username-Password-Authentication');

// Store the credentials afterward
final didStore =
    await auth0.credentialsManager.storeCredentials(credentials);
Add an audience and scope values
final credentials = await auth0.api.passkeyCredentialExchange(
    challenge: challenge,
    credential: credential,
    connection: 'Username-Password-Authentication',
    audience: 'YOUR_AUTH0_API_IDENTIFIER',
    scopes: {'profile', 'email', 'offline_access', 'read:todos'});

Sign up with passkeys

This feature is available on iOS 16.6+ and Android 9+ (API 28) only.

Passkeys let users register with a biometric or device PIN instead of a password, using the platform authenticator (Face ID / Touch ID on iOS, the Credential Manager on Android).

⚠️ Passkeys require additional configuration on both your Auth0 tenant and your app:

  • Set up a custom domain for your tenant. Passkeys will not work without one, since the relying-party domain must be a domain you own and can host the associated domain / Digital Asset Links file on.
  • Enable passkeys for your database connection and the Passkey grant type for your application. See Configure passkeys.
  • Configure the associated domain (iOS/macOS) and the equivalent Digital Asset Links file (Android) so the OS associates your app with the relying-party domain.

The SDK exposes two methods for passkey signup — passkeySignupChallenge and passkeyCredentialExchange — and leaves presenting the OS passkey UI to your app. The flow is:

  1. Request a registration challenge from Auth0 with passkeySignupChallenge.
  2. In your app, present the platform authenticator using that challenge and obtain a WebAuthn attestation. The SDK does not do this step — call the OS APIs directly (for example, ASAuthorizationController on iOS/macOS or Credential Manager on Android, typically over your own platform channel), then map the result into a PasskeyCredential.
  3. Exchange that credential for Auth0 tokens with passkeyCredentialExchange — the same method used for login.

You can identify the new user with any combination of email, phoneNumber, username, name, givenName, familyName, nickname, and picture, depending on how your connection is configured.

// 1. Request a registration challenge from Auth0. You can identify the new
//    user with any combination of email, phoneNumber, username, name,
//    givenName, familyName, nickname, and picture.
final challenge = await auth0.api.passkeySignupChallenge(
    email: 'jane.smith@example.com',
    name: 'Jane Smith',
    givenName: 'Jane',
    familyName: 'Smith',
    connection: 'Username-Password-Authentication');

// 2. Present the OS passkey-creation UI in your app (not provided by the SDK)
//    using `challenge.authParamsPublicKey`, then build a PasskeyCredential from
//    the resulting WebAuthn attestation. All values are base64url-encoded.
final credential = PasskeyCredential(
    id: '<base64url credentialId>',
    rawId: '<base64url credentialId>',
    type: 'public-key',
    authenticatorAttachment: 'platform',
    response: PasskeyAuthenticatorResponse(
        clientDataJSON: '<base64url clientDataJSON>',
        attestationObject: '<base64url attestationObject>'));

// 3. Exchange the credential for Auth0 tokens.
final credentials = await auth0.api.passkeyCredentialExchange(
    challenge: challenge,
    credential: credential,
    connection: 'Username-Password-Authentication');

// Store the credentials afterward
final didStore =
    await auth0.credentialsManager.storeCredentials(credentials);
Add an audience and scope values
final credentials = await auth0.api.passkeyCredentialExchange(
    challenge: challenge,
    credential: credential,
    connection: 'Username-Password-Authentication',
    audience: 'YOUR_AUTH0_API_IDENTIFIER',
    scopes: {'profile', 'email', 'offline_access', 'read:todos'});

Passwordless Login

Passwordless is a two-step authentication flow that requires the Passwordless OTP grant to be enabled for your Auth0 application. Check our documentation for more information.

1. Start the passwordless flow

Request a code to be sent to the user's email or phone number. For email scenarios, a link can be sent in place of the code.

await auth0.api.startPasswordlessWithEmail(
    email: "support@auth0.com", passwordlessType: PasswordlessType.code);
Using PhoneNumber
await auth0.api.startPasswordlessWithPhoneNumber(
    phoneNumber: "123456789", passwordlessType: PasswordlessType.code);

2. Login with the received code

To complete the authentication, you must send back that code the user received along with the email or phone number used to start the flow.

final credentials = await auth0.api.loginWithEmailCode(
          email: "support@auth0.com", verificationCode: "000000");
Using SMS
final credentials = await auth0.api.loginWithSmsCode(
    phoneNumber: "123456789", verificationCode: "000000");

Note

Sending additional parameters is supported only on iOS at the moment.

Retrieve user information

Fetch the latest user information from the /userinfo endpoint.

This method will yield a UserProfile instance. Check the API documentation to learn more about its available properties.

// Basic usage with Bearer token (default)
final userProfile = await auth0.api.userProfile(accessToken: accessToken);

// With explicit token type (useful for DPoP tokens)
final credentials = await auth0.credentialsManager.credentials();
final userProfile = await auth0.api.userProfile(
  accessToken: credentials.accessToken,
  tokenType: credentials.tokenType, // 'Bearer' or 'DPoP'
);

The tokenType parameter specifies the type of token being used:

  • 'Bearer' (default): Standard OAuth 2.0 bearer tokens
  • 'DPoP': DPoP (Demonstrating Proof of Possession) tokens for enhanced security

💡 When using DPoP tokens, the SDK automatically handles proof generation. See the DPoP documentation for more information.

Renew credentials

Use a refresh token to renew the user's credentials. It's recommended that you read and understand the refresh token process beforehand.

final newCredentials =
    await auth0.api.renewCredentials(refreshToken: refreshToken);

// Store the credentials afterward
final didStore =
    await auth0.credentialsManager.storeCredentials(newCredentials);

💡 To obtain a refresh token, make sure your Auth0 application has the refresh token grant enabled. If you are also specifying an audience value, make sure that the corresponding Auth0 API has the Allow Offline Access setting enabled.

Custom Token Exchange

Custom Token Exchange allows you to enable applications to exchange their existing tokens for Auth0 tokens when calling the /oauth/token endpoint. This is useful for advanced integration use cases, such as:

  • Integrate an external identity provider
  • Migrate to Auth0

Note: This feature is currently available in Early Access. Please reach out to Auth0 support to enable it for your tenant.

Mobile (Android/iOS)
final credentials = await auth0.api.customTokenExchange(
  subjectToken: 'external-idp-token',
  subjectTokenType: 'urn:acme:legacy-token',
  audience: 'https://api.example.com', // Optional
  scopes: {'openid', 'profile', 'email'}, // Optional, defaults to {'openid', 'profile', 'email'}
  organization: 'org_abc123', // Optional
);
Web
final credentials = await auth0Web.customTokenExchange(
  subjectToken: 'external-idp-token',
  subjectTokenType: 'urn:acme:legacy-token',
  audience: 'https://api.example.com', // Optional
  scopes: {'openid', 'profile', 'email'}, // Optional
  organizationId: 'org_abc123', // Optional
);

💡 For more information, see the Custom Token Exchange documentation and RFC 8693.

Errors

The Authentication API client will only throw ApiException exceptions. You can find more information in the details property of the exception. Check the API documentation to learn more about the available ApiException properties.

try {
  final credentials = await auth0.api.login(
      usernameOrEmail: email,
      password: password,
      connectionOrRealm: connection);
  // ...
} on ApiException catch (e) {
  if (e.isRetryable) {
    // Transient error (e.g. network issue) — safe to retry
  } else {
    print(e);
  }
}

The isRetryable property indicates whether the error is transient (e.g. a network outage) and the operation can be retried. It returns true when isNetworkError is true.

Go up ⤴

🌐📱 Organizations

Organizations is a set of features that provide better support for developers who build and maintain SaaS and Business-to-Business (B2B) applications.

💡 Organizations is currently only available to customers on our Enterprise and Startup subscription plans.

Log in to an organization

Mobile
final credentials = await auth0
    .webAuthentication()
    .login(organizationId: 'YOUR_AUTH0_ORGANIZATION_ID');
Web
await auth0Web.loginWithRedirect(
    redirectUrl: 'http://localhost:3000',
    organizationId: 'YOUR_AUTH0_ORGANIZATION_ID');

Accept user invitations

To accept organization invitations your app needs to support deep linking, as invitation links are HTTPS-only. Tapping on the invitation link should open your app.

When your app gets opened by an invitation link, grab the invitation URL and pass it to the login method.

Mobile
final credentials =
    await auth0.webAuthentication().login(invitationUrl: url);
Web
await auth0Web.loginWithRedirect(
    redirectUrl: 'http://localhost:3000',
    invitationUrl: url);

Go up ⤴

📱 Bot detection

This example is mobile/macOS only; the SPA SDK used by auth0_flutter does not include an API client.

If you are performing database login/signup via the Authentication API and would like to use the Bot Detection feature, you need to handle the isVerificationRequired error. It indicates that the request was flagged as suspicious and an additional verification step is necessary to log the user in. That verification step is web-based, so you need to use Web Auth to complete it.

try {
  final credentials = await auth0.api.login(
      usernameOrEmail: email,
      password: password,
      connectionOrRealm: connection,
      scopes: scopes);
  // ...
} on ApiException catch (e) {
  if (e.isVerificationRequired) {
    final credentials = await auth0.webAuthentication().login(
        scopes: scopes,
        useEphemeralSession: true, // Otherwise a session cookie will remain (iOS/macOS only)
        parameters: {
          'connection': connection,
          'login_hint': email // So the user doesn't have to type it again
        });
    // ...
  }
}

Go up ⤴

📱 My Account API

The My Account API lets authenticated users manage their own multi-factor authentication (MFA) methods — enrolling, confirming, listing, updating, and deleting factors such as phone, email, TOTP, push notifications, and recovery codes. It is available on mobile (Android/iOS) only.

💡 The My Account API must be enabled for your tenant. If it is not yet available on your account, reach out to Auth0 support to get it enabled.

Obtaining an access token for the My Account API

The My Account API requires an access token issued specifically for the https://YOUR_DOMAIN/me/ audience, with the scopes for the operations you intend to perform.

The recommended approach is to log in once for your application with the offline_access scope (so a refresh token is stored), and then exchange that refresh token for a My Account–scoped access token — instead of launching a second interactive login. This is the same pattern the other Auth0 SDKs follow (react-native-auth0, Auth0.swift, and Auth0.Android).

// 1. Log in once for your app, requesting offline_access to get a refresh token.
final credentials = await auth0.webAuthentication().login(
  scopes: {'openid', 'profile', 'email', 'offline_access'},
);
await auth0.credentialsManager.storeCredentials(credentials);

// 2. Exchange the stored refresh token for a token scoped to the My Account API,
//    by requesting the `https://YOUR_DOMAIN/me/` audience and the My Account scopes.
final myAccountCredentials = await auth0.credentialsManager.getApiCredentials(
  audience: 'https://YOUR_DOMAIN/me/',
  scope: {
    'read:me:authentication_methods',
    'create:me:authentication_methods',
    'update:me:authentication_methods',
    'delete:me:authentication_methods',
    'read:me:factors',
  },
);

// 3. Create the My Account client with the resulting access token.
final myAccount = auth0.myAccount(
  accessToken: myAccountCredentials.accessToken,
);

💡 getApiCredentials returns a separate, audience-scoped token via a Multi-Resource Refresh Token (MRRT) exchange; it does not replace the application credentials stored via storeCredentials. The token is cached per audience, so subsequent calls return the cached value until it expires.

⚠️ Exchanging the refresh token requires MRRT to be enabled for your tenant, and the application must have requested offline_access at login so that a refresh token is available.

Listing and managing authentication methods

// List all enrolled MFA methods.
final methods = await myAccount.getAuthenticationMethods();

// Optionally filter by type.
final phones = await myAccount.getAuthenticationMethods(
  type: AuthenticationMethodType.phone,
);

// Retrieve a single method by id.
final method = await myAccount.getAuthenticationMethod(id: 'method_id');

// List the factors available for enrollment on the tenant.
final factors = await myAccount.getFactors();

// Update a method's display name and/or preferred phone channel.
await myAccount.updateAuthenticationMethod(
  id: 'method_id',
  name: 'My personal phone',
  preferredAuthenticationMethod: PhoneType.voice,
);

// Delete a method.
await myAccount.deleteAuthenticationMethod(id: 'method_id');

Enrolling a factor with OTP (phone, email, TOTP)

Phone, email, and TOTP enrollments are completed by verifying a one-time password with verifyOtp. Pass the factorType of the factor you enrolled so the correct confirmation endpoint is used.

// Start phone enrollment (an OTP is sent via SMS).
final challenge = await myAccount.enrollPhone(
  phoneNumber: '+1234567890',
  type: PhoneType.sms,
);

// Confirm with the OTP the user received.
final method = await myAccount.verifyOtp(
  id: challenge.id,
  authSession: challenge.authSession,
  otp: '123456',
  factorType: 'phone', // 'phone' | 'email' | 'totp'
);

The same two-step flow applies to enrollEmail (factorType: 'email') and enrollTotp (factorType: 'totp').

Enrolling a factor without OTP (push, recovery code)

Push notification and recovery code enrollments do not use an OTP — they are completed with confirmEnrollment.

// Start push enrollment.
final challenge = await myAccount.enrollPush();

// ...complete the out-of-band step (e.g. the user approves on their device), then:
final method = await myAccount.confirmEnrollment(
  id: challenge.id,
  authSession: challenge.authSession,
  factorType: 'push-notification', // 'push-notification' | 'recovery-code'
);

The same flow applies to enrollRecoveryCode (factorType: 'recovery-code').

Enrolling a passkey

A signed-in user can add a passkey as a new authentication method. Like passkey login and signup, this is a two-step flow and the SDK leaves presenting the OS passkey UI to your app:

  1. Request an enrollment challenge with enrollPasskeyChallenge.
  2. In your app, present the platform authenticator using challenge.authParamsPublicKey to create a passkey, and map the resulting WebAuthn attestation into a PasskeyCredential. The SDK does not do this step — call the OS APIs directly (for example, ASAuthorizationController on iOS/macOS or Credential Manager on Android, typically over your own platform channel).
  3. Submit the credential with enrollPasskey to complete the enrollment.

⚠️ Passkeys require a custom domain on your tenant and additional configuration. See Sign up with passkeys for details.

The access token must include the create:me:authentication_methods scope.

// 1. Request an enrollment challenge.
final challenge = await myAccount.enrollPasskeyChallenge();

// 2. Present the OS passkey creation UI in your app (not provided by the SDK)
//    using `challenge.authParamsPublicKey`, then build a PasskeyCredential from
//    the resulting WebAuthn attestation.
final credential = PasskeyCredential(
  id: '...',
  rawId: '...',
  type: 'public-key',
  response: PasskeyAuthenticatorResponse(
    clientDataJSON: '...',
    attestationObject: '...',
  ),
);

// 3. Submit the credential to complete the enrollment.
final method = await myAccount.enrollPasskey(
  challenge: challenge,
  credential: credential,
);

print('Enrolled passkey: ${method.id} (${method.relyingPartyId})');

Using DPoP

To secure My Account API requests with DPoP (Demonstrating Proof-of-Possession) sender-constrained tokens, set useDPoP to true when creating the client. It defaults to false. The DPoP key pair is generated and stored securely on the device (Keychain on iOS, Keystore on Android).

final myAccount = auth0.myAccount(
  accessToken: myAccountCredentials.accessToken,
  useDPoP: true,
);

Errors

My Account API calls throw a MyAccountException on failure.

try {
  await myAccount.getAuthenticationMethods();
} on MyAccountException catch (e) {
  print('${e.code}: ${e.message} (${e.statusCode})');
}

Go up ⤴

📱 Multi-Factor Authentication (MFA)

Note: This feature is currently available in Early Access. Please reach out to Auth0 support to enable it for your tenant.

The MFA API lets you complete a multi-factor authentication flow using an mfa_token — Auth0's flexible/expanded grant support. It is available on mobile (Android/iOS) only; Web and Windows are not supported.

Unlike the My Account API — which manages a signed-in user's authenticators — the MFA API is used mid-login, when a token request fails because MFA is required. You use the mfa_token from that failure to list, challenge, enroll, and verify a factor, and the successful verification returns the user's Credentials.

Obtaining an mfa_token

When an authentication request (for example a database login or a credentials renewal) requires a second factor, the SDK throws an ApiException whose isMultifactorRequired flag is true and which carries an mfaToken. Pass that token to auth0.mfa(...) to start the flow.

final auth0 = Auth0('YOUR_DOMAIN', 'YOUR_CLIENT_ID');

try {
  await auth0.api.login(
    usernameOrEmail: 'user@example.com',
    password: 'secret',
    connectionOrRealm: 'Username-Password-Authentication',
    scopes: {'openid', 'profile', 'email'},
  );
} on ApiException catch (e) {
  if (e.isMultifactorRequired && e.mfaToken != null) {
    final mfa = auth0.mfa(mfaToken: e.mfaToken!);

    // `mfaRequirements` (when present) tells you which factors the user can
    // be challenged with, and which they can newly enroll.
    final requirements = e.mfaRequirements;
    print('Can challenge: '
        '${requirements?.challenge.map((f) => f.type).toList()}');
    print('Can enroll: '
        '${requirements?.enroll.map((f) => f.type).toList()}');

    // ...drive the challenge or enrollment flow with `mfa` (see below).
  }
}

Listing authenticators and challenging a factor

If the user already has authenticators enrolled, list them and trigger a challenge on the one they choose. For out-of-band factors (SMS, Voice, Email, Push) the challenge delivers the code and returns an oobCode; for TOTP you verify the code directly without challenging.

final authenticators = await mfa.getAuthenticators();

// Optionally narrow the results to specific factor types.
final oobOnly = await mfa.getAuthenticators(factorsAllowed: ['oob']);

final selected = authenticators.first;
final challenge = await mfa.challenge(authenticatorId: selected.id);

// `challenge.oobCode` is used to verify out-of-band factors (see below).
// When `challenge.bindingMethod == 'prompt'`, the user must also enter the
// code they received as the `bindingCode`.

Enrolling a new factor

If the user has no suitable authenticator yet, enroll one. Each enrollment returns an MfaEnrollmentChallenge; which of its fields are populated depends on the factor.

// TOTP (authenticator app): render `barcodeUri` as a QR code (or show
// `totpSecret`), then verify with the OTP from the app.
final totp = await mfa.enrollTotp();
// totp.barcodeUri, totp.totpSecret, totp.recoveryCodes

// Phone (SMS by default, or Voice): an OOB code is sent to the number.
final phone = await mfa.enrollPhone(
  phoneNumber: '+1234567890',
  type: PhoneType.sms, // or PhoneType.voice
);

// Email: an OOB code is sent to the address.
final email = await mfa.enrollEmail(email: 'user@example.com');

// Push (Auth0 Guardian): render `barcodeUri` for the user to scan.
final push = await mfa.enrollPush();

Verifying and exchanging for credentials

Verifying completes the flow and exchanges the mfa_token for the user's Credentials. Pick the method that matches the factor:

// TOTP — the code from the authenticator app.
final credentials = await mfa.verifyOtp(otp: '123456');

// Out-of-band (SMS, Voice, Email, Push) — the `oobCode` from the challenge
// (or enrollment). Provide `bindingCode` when the challenge's bindingMethod
// is `prompt` (the user enters the code they received).
final credentials = await mfa.verifyOob(
  oobCode: challenge.oobCode!,
  bindingCode: '123456',
);

// Recovery code — a one-time code the user saved during enrollment.
final credentials = await mfa.verifyRecoveryCode(recoveryCode: 'ABCD1234...');

// On success, persist the credentials as usual.
await auth0.credentialsManager.storeCredentials(credentials);

Errors

MFA API calls throw an MfaException on failure. It exposes convenience getters for the common cases so you can branch without inspecting raw error codes.

try {
  final credentials = await mfa.verifyOtp(otp: '000000');
} on MfaException catch (e) {
  if (e.isMfaTokenExpired) {
    // The mfa_token is no longer valid — restart the login flow.
  } else if (e.isInvalidCode) {
    // The user entered a wrong/expired code — let them retry.
  } else if (e.isNetworkError) {
    // Transient — `e.isRetryable` is true.
  } else {
    print('${e.code}: ${e.message}');
  }
}

Go up ⤴