Examples

July 8, 2026 · View on GitHub


Web Auth (iOS / macOS / visionOS)

See all the available features in the API documentation ↗

Note

All completion callbacks in Auth0.swift execute on the main thread, making it safe to update UI directly. If needed, explicitly dispatch to a background thread.

Web Auth signup

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.

ParametersNo existing sessionExisting session
No extra parametersShows the login pageRedirects to the callback URL
"screen_hint": "signup"Shows the signup pageRedirects to the callback URL
"prompt": "login"Shows the login pageShows the login page
"prompt": "login", "screen_hint": "signup"Shows the signup pageShows the signup page
Auth0
    .webAuth()
    .parameters(["screen_hint": "signup"])
    .start { result in
        switch result {
        case .success(let credentials):
            print("Obtained credentials: \(credentials)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }

Note

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.

Using async/await
do {
    let credentials = try await Auth0
        .webAuth()
        .parameters(["screen_hint": "signup"])
        .start()
    print("Obtained credentials: \(credentials)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .webAuth()
    .parameters(["screen_hint": "signup"])
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { credentials in
        print("Obtained credentials: \(credentials)")
    })
    .store(in: &cancellables)

Web Auth configuration

The following are some of the available Web Auth configuration options. Check the API documentation for the full list.

Important

Each configuration method returns a new copy of the WebAuth instance — it does not modify the original. Always use method chaining, or reassign the return value when configuring conditionally.

The following pattern does not work — audience is called but its return value is discarded, so it is never applied:

// ⚠️ Does not work
var webAuth = Auth0.webAuth().scope("openid")
webAuth.audience("https://api.example.com") // return value discarded — has no effect
webAuth.start { result in ... }

Instead, either chain all options in a single expression:

// ✅ Recommended — chain everything
Auth0
    .webAuth()
    .scope("openid")
    .audience("https://api.example.com")
    .start { result in ... }

Or reassign the return value when you need to configure conditionally:

// ✅ Conditional configuration — reassign the return value
var webAuth = Auth0.webAuth().scope("openid")
webAuth = webAuth.audience("https://api.example.com")
webAuth.start { result in ... }

Use any Auth0 connection

Specify an Auth0 connection to directly open that identity provider's login page, skipping the Universal Login page itself. The connection must first be enabled for your Auth0 application in the Dashboard.

Auth0
    .webAuth()
    .connection("github") // Show the GitHub login page
    // ...

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.

Auth0
    .webAuth()
    .audience("YOUR_AUTH0_API_IDENTIFIER")
    // ...

Add a scope value

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

Auth0
    .webAuth()
    .scope("openid profile email read:todos")
    // ...

Use connectionScope() to configure a scope value for an Auth0 connection.

Auth0
    .webAuth()
    .connection("github")
    .connectionScope("public_repo read:user")
    // ...

Get a refresh token

The default scope already includes offline_access, so a refresh token is requested automatically. If you are specifying a custom scope, include offline_access explicitly:

Auth0
    .webAuth()
    .scope("openid profile email offline_access read:todos")
    // ...

To opt out of refresh tokens, specify a scope without offline_access:

Auth0
    .webAuth()
    .scope("openid profile email")
    // ...

Important

Make sure that 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.

Use a custom URLSession instance

You can specify a custom URLSession instance for more advanced networking configuration, such as customizing timeout values.

Auth0
    .webAuth(session: customURLSession)
    // ...

Note

This custom URLSession instance will be used when communicating with the Auth0 Authentication API, not when opening the Universal Login page.

Specify a presentation window

When building apps that support multiple windows (such as iPad apps with Split View or Stage Manager, or macOS apps with multiple windows), you can specify which window should present the authentication UI using the presentationWindow() method.

Using the UIKit app lifecycle

iOS / iPadOS:

guard let window = view.window else { return }

Auth0
    .webAuth()
    .presentationWindow(window) // Pass the UIWindow
    .start { result in
        // ...
    }

macOS:

guard let window = view.window else { return }

Auth0
    .webAuth()
    .presentationWindow(window) // Pass the NSWindow
    .start { result in
        // ...
    }
Using the SwiftUI app lifecycle

iOS / iPadOS:

import SwiftUI
import Auth0

struct ContentView: View {
    @Environment(\.window) private var window // Custom environment key

    var body: some View {
        Button("Login") {
            Task {
                var webAuth = Auth0.webAuth()

                if let window = window {
                    webAuth = webAuth.presentationWindow(window)
                }

                let credentials = try await webAuth.start()
                // Handle credentials...
            }
        }
    }
}

// MARK: - Window Environment Setup

@main
struct YourApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .withWindowReader() // Enable window tracking
        }
    }
}

// Window tracking infrastructure
private struct WindowKey: EnvironmentKey {
    static let defaultValue: UIWindow? = nil
}

extension EnvironmentValues {
    var window: UIWindow? {
        get { self[WindowKey.self] }
        set { self[WindowKey.self] = newValue }
    }
}

struct WindowReaderModifier: ViewModifier {
    @State private var window: UIWindow?

    func body(content: Content) -> some View {
        content
            .environment(\.window, window)
            .background(WindowAccessor(window: $window))
    }
}

struct WindowAccessor: UIViewRepresentable {
    @Binding var window: UIWindow?

    func makeUIView(context: Context) -> UIView {
        let view = UIView()
        view.backgroundColor = .clear
        return view
    }

    func updateUIView(_ uiView: UIView, context: Context) {
        DispatchQueue.main.async {
            self.window = uiView.window
        }
    }
}

extension View {
    func withWindowReader() -> some View {
        self.modifier(WindowReaderModifier())
    }
}

macOS:

import SwiftUI
import Auth0

struct ContentView: View {
    @State private var currentWindow: NSWindow?

    var body: some View {
        Button("Login") {
            Task {
                var webAuth = Auth0.webAuth()

                if let window = currentWindow {
                    webAuth = webAuth.presentationWindow(window)
                }

                let credentials = try await webAuth.start()
                // Handle credentials...
            }
        }
        .onAppear {
            currentWindow = getCurrentWindow()
        }
    }

    private func getCurrentWindow() -> NSWindow? {
        if let keyWindow = NSApplication.shared.keyWindow {
            return keyWindow
        }
        if let mainWindow = NSApplication.shared.mainWindow {
            return mainWindow
        }
        return NSApplication.shared.windows.first
    }
}

You can also specify a presentation window when using the SFSafariViewController or WKWebView providers:

// SFSafariViewController
Auth0
    .webAuth()
    .provider(WebAuthentication.safariProvider(presentationWindow: window))
    // ...

// WKWebView
Auth0
    .webAuth()
    .provider(WebAuthentication.webViewProvider(presentationWindow: window))
    // ...

Note

If you don't specify a presentation window, Auth0.swift will automatically use the foreground active scene's key window for multi-window iPad apps.

Use SFSafariViewController instead of ASWebAuthenticationSession

You can use the built-in SFSafariViewController Web Auth provider to open the Universal Login page.

Auth0
    .webAuth()
    .provider(WebAuthentication.safariProvider()) // Use SFSafariViewController
    .start { result in
        // ...
    }

Tip

See ASWebAuthenticationSession vs SFSafariViewController (iOS) to help determine which option best suits your use case, depending on your requirements.

Note

SFSafariViewController does not support using Universal Links as callback URLs.

The SFSafariViewController Web Auth provider requires 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 Web Auth operation.

1. Configure a custom URL scheme

In Xcode, go to the Info tab of your app target settings. In the URL Types section, click the button to add a new entry. There, enter auth0 into the Identifier field and $(PRODUCT_BUNDLE_IDENTIFIER) into the URL Schemes field.

Screenshot of the URL Types section inside the app target settings

This registers your bundle identifier as a custom URL scheme, so the callback URL can reach your app.

2. Capture the callback URL
Using the UIKit app lifecycle
// AppDelegate.swift

func application(_ app: UIApplication,
                 open url: URL,
                 options: [UIApplication.OpenURLOptionsKey: Any]) -> Bool {
    return WebAuthentication.resume(with: url)
}
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 the SwiftUI app lifecycle
SomeView()
    .onOpenURL { url in
        WebAuthentication.resume(with: url)
    }
Logout

SFSafariViewController should only be used for login. 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 clear() 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.

Auth0
    .webAuth()
    .provider(WebAuthentication.safariProvider())
    .parameters(["prompt": "login"])
    .start { result in
        // ...
    }

Use WKWebView instead of ASWebAuthenticationSession

You can also use the built-in WKWebView Web Auth provider to open the Universal Login page. Unlike SFSafariViewController, WKWebView supports using Universal Links as callback URLs.

Auth0
    .webAuth()
    .provider(WebAuthentication.webViewProvider()) // Use WKWebView
    .start { result in
        // ...
    }

Note

To use Universal Login's biometrics and passkeys with WKWebView, you must set up an associated domain.

Warning

The use of WKWebView for performing web-based authentication is not recommended, and some social identity providers –such as Google– do not support it.

ID token validation

Auth0.swift 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.

For direct Authentication API calls (login, renew, codeExchange, etc.) and MFA verification calls, ID token validation is opt-in. All credential-returning methods return any TokenRequestable, and you can chain .validateClaims() before .start(_:) to enable it:

Auth0
    .authentication()
    .renew(withRefreshToken: credentials.refreshToken)
    .validateClaims()
    .start { result in
        switch result {
        case .success(let credentials): print("Renewed: \(credentials)")
        case .failure(let error): print("Error: \(error)")
        }
    }

You can customise the validation by chaining additional modifiers after validateClaims():

Auth0
    .authentication()
    .codeExchange(withCode: code, codeVerifier: verifier, redirectURI: redirectURI)
    .validateClaims()
    .withLeeway(120)                            // clock-skew tolerance in seconds
    .withNonce(nonce)                           // expected nonce claim
    .withOrganization("org_abc123")             // expected org_id or org_name claim
    .start { result in ... }

The same API is available on MFA verification:

Auth0
    .mfa()
    .verify(otp: otp, mfaToken: mfaToken)
    .validateClaims()
    .start { result in ... }

DPoP [EA]

DPoP (Demonstrating Proof of Possession) is an application-level mechanism for sender-constraining OAuth 2.0 access and refresh tokens by proving that the app is in possession of a certain private key. You can enable it by calling the useDPoP() method.

Auth0
    .webAuth()
    .useHTTPS()
    .useDPoP()
    .start { result in
        switch result {
        case .success(let credentials):
            print("Obtained credentials: \(credentials)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }

Important

DPoP will only be used for new user sessions created after enabling it. DPoP will not be applied to any requests involving existing access and refresh tokens (such as exchanging the refresh token for new credentials).

This means that, after you've enabled it in your app, DPoP will only take effect when users log in again. It's up to you to decide how to roll out this change to your users. For example, you might require users to log in again the next time they open your app. You'll need to implement the logic to handle this transition based on your app's requirements.

When making requests to your own APIs, use the DPoP.addHeaders() method to add the Authorization and DPoP headers to a URLRequest. The Authorization header is set using the access token and token type, while the DPoP header contains the generated DPoP proof.

var request = URLRequest(url: URL(string: "https://example.com/api/endpoint")!)
request.httpMethod = "POST"

try DPoP.addHeaders(to: &request,
                    accessToken: credentials.accessToken,
                    tokenType: credentials.tokenType)

If your API is issuing DPoP nonces to prevent replay attacks, you can pass the nonce value to the addHeaders() method to include it in the DPoP proof. Use the DPoP.isNonceRequired(by:) method to check if a particular API response failed because a nonce is required.

if DPoP.isNonceRequired(by: response), 
    let nonce = response.value(forHTTPHeaderField: "DPoP-Nonce") {
    try DPoP.addHeaders(to: &request,
                        accessToken: credentials.accessToken,
                        tokenType: credentials.tokenType,
                        nonce: nonce)

    // Retry the request with the new DPoP proof that includes the nonce
}

On logout, you should call DPoP.clearKeypair() to delete the user's key pair from the Keychain.

Auth0.webAuth()
    .useHTTPS()
    .logout { result in
    // ...
}

try credentialsManager.clear()
try DPoP.clearKeypair()

Note


When logging out, you do not need to call useDPoP() as it has no effect during the logout process.

Automatic credentials management

You can pass a CredentialsManager instance to the Web Auth client to automatically store credentials after a successful login and clear them after a successful logout. If no credentials manager is set, credentials are returned directly without being stored.

let credentialsManager = CredentialsManager(authentication: Auth0.authentication())

// Credentials are automatically stored after login
let credentials = try await Auth0
    .webAuth()
    .useCredentialsManager(credentialsManager)
    .start()

// Later, retrieve stored credentials using the same instance
let storedCredentials = try await credentialsManager.credentials()

// Credentials are automatically cleared after logout
try await Auth0
    .webAuth()
    .useCredentialsManager(credentialsManager)
    .logout()

Important

Call useCredentialsManager(_:) on both your start() and logout() call chains. Omitting it on logout() will succeed but credentials will not be cleared automatically. Do not manually call store(credentials:) after login or clear() after logout on the same instance — doing so can lead to race conditions or inconsistent state.

Note

If the credentials manager fails to store or clear credentials, a WebAuthError.credentialsManagerError will be thrown. The underlying error can be accessed via the cause property.

Web Auth errors

Web Auth will only produce WebAuthError error values. You can find the underlying error (if any) in the cause: Error? property of the WebAuthError. Not all error cases will have an underlying cause. Check the API documentation to learn more about the error cases you need to handle, and which ones include a cause value.

Warning

Do not parse or otherwise rely on the error messages to handle the errors. The error messages are not part of the API and can change. Run a switch statement on the error cases instead, which are part of the API.

Error handling example

Auth0
    .webAuth()
    .start { result in
        switch result {
        case .success(let credentials):
            print("Obtained credentials: \(credentials)")
            
        case .failure(let error):
            switch error {
            case .userCancelled:
                // User pressed the "Cancel" button
                print("User cancelled")
                
            case .authenticationFailed:
                // Authentication failed on the server side
                // Access the underlying AuthenticationError for details
                if let authError = error.cause as? AuthenticationError {
                    print("Auth failed: \(authError.info["error"] ?? "unknown")")
                }
                
            case .codeExchangeFailed:
                // Token exchange failed 
                if let authError = error.cause as? AuthenticationError {
                    print("Token exchange failed: \(authError.info["error"] ?? "unknown")")
                }
                
            case .unknown(let message):
                // Configuration errors or unexpected issues
                print("Unknown error: \(message)")
                
            default:
                print("Error: \(error)")
            }
        }
    }
Using async/await
do {
    let credentials = try await Auth0.webAuth().start()
    print("Obtained credentials: \(credentials)")
} catch let error as WebAuthError {
    switch error {
    case .userCancelled:
        print("User cancelled")
    case .authenticationFailed:
        if let authError = error.cause as? AuthenticationError {
            print("Auth failed: \(authError.info["error"] ?? "unknown")")
        }
    case .codeExchangeFailed:
        if let authError = error.cause as? AuthenticationError {
            print("Token exchange failed: \(authError.info["error"] ?? "unknown")")
        }
    case .unknown(let message):
        print("Unknown error: \(message)")
    default:
        print("Error: \(error)")
    }
} catch {
    print("Unexpected error: \(error)")
}
Using Combine
Auth0
    .webAuth()
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            switch error {
            case .userCancelled:
                print("User cancelled")
            case .authenticationFailed:
                if let authError = error.cause as? AuthenticationError {
                    print("Auth failed: \(authError.info["error"] ?? "unknown")")
                }
            case .codeExchangeFailed:
                if let authError = error.cause as? AuthenticationError {
                    print("Token exchange failed: \(authError.info["error"] ?? "unknown")")
                }
            case .unknown(let message):
                print("Unknown error: \(message)")
            default:
                print("Error: \(error)")
            }
        }
    }, receiveValue: { credentials in
        print("Obtained credentials: \(credentials)")
    })
    .store(in: &cancellables)

Go up ⤴

Credentials Manager (iOS / macOS / tvOS / watchOS / visionOS)

See all the available features in the API documentation ↗

Note

All completion callbacks in Auth0.swift execute on the main thread, making it safe to update UI directly. If needed, explicitly dispatch to a background thread.

The Credentials Manager utility allows you to securely store and retrieve the user's credentials from the Keychain.

let credentialsManager = CredentialsManager(authentication: Auth0.authentication())

Caution

The Credentials Manager is not thread-safe, except for the following methods:

  • credentials()
  • apiCredentials()
  • ssoCredentials()
  • renew()

To avoid concurrency issues, do not call its non thread-safe methods and properties from different threads without proper synchronization.

Note

Swift 6 Sendability Support: The Credentials Manager conforms to Sendable, which allows it to be passed across concurrency boundaries (like into actors). However, this does not make all its methods thread-safe. Only the methods listed above (credentials(), apiCredentials(), ssoCredentials(), renew()) are thread-safe. Other methods and properties still require proper synchronization when called from multiple threads.

// Example: Using CredentialsManager in an Actor (Swift 6)
actor AuthService {
    let credentialsManager: CredentialsManager
    
    init() {
        self.credentialsManager = CredentialsManager(authentication: Auth0.authentication())
    }
    
    func fetchCredentials() async throws -> Credentials {
        // Safe to call from within an actor
        return try await credentialsManager.credentials(withScope: "openid profile email",
                                                        parameters: [:],
                                                        headers: [:])
    }
}

The default minTTL is 60 seconds. You can override it if needed:

let credentials = try await credentialsManager.credentials(minTTL: 120)

Store credentials

When your users log in, store their credentials securely in the Keychain. You can then check if their credentials are still valid when they open your app again.

do {
    try credentialsManager.store(credentials: credentials)
} catch {
    print("Failed to store credentials: \(error)")
}

Check for stored credentials

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

If you are using refresh tokens

guard credentialsManager.canRenew() else {
    // No renewable credentials exist, present the login page
}
// Retrieve the stored credentials

See Get a refresh token to learn how to obtain a refresh token.

If you are not using refresh tokens

guard credentialsManager.hasValid() else {
    // No valid credentials exist, present the login page
}
// Retrieve the stored credentials

Retrieve stored credentials

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

See Get a refresh token to learn how to obtain a refresh token.

credentialsManager.credentials { result in 
    switch result {
    case .success(let credentials):
        print("Obtained credentials: \(credentials)")
    case .failure(let error):
        print("Failed with: \(error)") 
    }
}
Using async/await
do {
    let credentials = try await credentialsManager.credentials()
    print("Obtained credentials: \(credentials)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
credentialsManager
    .credentials()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { credentials in
        print("Obtained credentials: \(credentials)")
    })
    .store(in: &cancellables)

Caution

Do not call store(credentials:) afterward. The Credentials Manager automatically persists the renewed credentials. Since this method is thread-safe and store(credentials:) is not, calling it anyway can cause concurrency issues.

Caution

To ensure that no concurrent renewal requests get made, do not call this method from multiple Credentials Manager instances. The Credentials Manager cannot synchronize requests across instances.

Automatic retry on transient errors

The Credentials Manager includes automatic retry logic for credential renewal when transient errors occur. This helps handle scenarios where network requests fail temporarily, such as:

  • Network connectivity issues (timeouts, connection lost, DNS failures)
  • Rate limiting responses (HTTP 429)
  • Server errors (HTTP 5xx)

How it works:

When a renewal request fails due to a transient error, the Credentials Manager will automatically retry the request with exponential backoff (0.5s, 1s, 2s, 4s, etc.). This addresses the following scenario:

  1. Request A calls credentials() and starts a token refresh
  2. Request A successfully hits the server and gets new credentials
  3. Request A fails on the way back (network issue), never reaching the client
  4. The retry mechanism automatically retries the failed request using the same (old) refresh token

To fully leverage the retry mechanism, ensure your Auth0 tenant's Rotation Overlap Period is set to at least 180 seconds. This overlap window ensures the old refresh token remains valid during retry attempts even if the backend resource was already updated. You can configure this setting in your Auth0 Dashboard under Applications > [Your Application] > Settings > Refresh Token Rotation.

Configure retry behavior:

By default, retries are disabled. You can enable retries by specifying a maximum retry count when creating the Credentials Manager. It is advisable to set a maximum of 2 retries, which provides sufficient resilience without introducing excessive delays or unnecessary network requests.

// Enable 1 retry attempt
let credentialsManager = CredentialsManager(
    authentication: Auth0.authentication(),
    maxRetries: 1
)

Important considerations:

  • Retries only occur for transient errors (network issues, rate limiting, server errors)
  • Permanent errors (invalid refresh token, authorization failures) will not be retried
  • Each retry uses exponential backoff to avoid overwhelming the server
  • The 180-second refresh token overlap window ensures retries can succeed even after a successful backend renewal

Renew stored credentials

The credentials() method automatically renews the stored credentials when needed, using the refresh token. However, you can also force a renewal using the renew() method. This method is thread-safe.

See Get a refresh token to learn how to obtain a refresh token.

credentialsManager.renew { result in
    switch result {
    case .success(let credentials):
        print("Renewed credentials: \(credentials)")
    case .failure(let error):
        print("Failed with: \(error)")
    }
}
Using async/await
do {
    let credentials = try await credentialsManager.renew()
    print("Renewed credentials: \(credentials)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
credentialsManager
    .renew()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { credentials in
        print("Renewed credentials: \(credentials)")
    })
    .store(in: &cancellables)

Caution

Do not call store(credentials:) afterward. The Credentials Manager automatically persists the renewed credentials. Since this method is thread-safe and store(credentials:) is not, calling it anyway can cause concurrency issues.

Caution

To ensure that no concurrent renewal requests get made, do not call this method from multiple Credentials Manager instances. The Credentials Manager cannot synchronize requests across instances.

Retrieve stored user information

The stored ID token contains a copy of the user information at the time of authentication (or renewal, if the credentials were renewed). That user information can be retrieved from the Keychain synchronously, without checking if the credentials expired.

do {
    let user = try credentialsManager.userProfile()
} catch {
    print("Failed to retrieve user profile: \(error)")
}

To get the latest user information, you can use the renew() method. Calling this method will automatically update the stored user information. You can also use the userInfo(withAccessToken:) method of the Authentication API client, but it will not update the stored user information.

Clear stored credentials

The stored credentials can be removed from the Keychain by using the clear() method.

do {
    try credentialsManager.clear()
} catch {
    print("Failed to clear credentials: \(error)")
}

Clear all stored credentials

To remove all credentials stored by the Credentials Manager from the Keychain —including the default credentials entry and any API credentials stored for different audiences— use the clearAll() method.

do {
    try credentialsManager.clearAll()
} catch {
    print("Failed to clear all credentials: \(error)")
}

Note

clearAll() delegates to the underlying storage's deleteAllEntries() method, which removes all entries for the configured service/access group. Ensure the storage is dedicated to Auth0 credentials to avoid unintended data loss.

This is different from clear(), which only removes the default credentials entry.

Biometric authentication

You can enable an additional level of user authentication before retrieving credentials using the biometric authentication supported by the device, such as Face ID or Touch ID.

credentialsManager.enableBiometrics(withTitle: "Unlock with Face ID")

If needed, you can specify a particular LAPolicy to be used. For example, you might want to support Face ID or Touch ID, but also allow fallback to passcode.

credentialsManager.enableBiometrics(withTitle: "Unlock with Face ID or passcode", 
                                    evaluationPolicy: .deviceOwnerAuthentication)

Biometric Policy

You can configure a BiometricPolicy to control when biometric authentication is required. There are four types of policies available:

  • .default: Uses the same LAContext instance, allowing the system to manage biometric prompts. The system may skip the prompt if biometric authentication was recently successful. This is the default policy and preserves backward-compatible behavior.
  • .always: Requires biometric authentication every time credentials are accessed. Creates a fresh LAContext for each authentication to ensure a new prompt is always shown.
  • .session(timeoutInSeconds:): Requires biometric authentication only if the specified time (in seconds) has passed since the last successful authentication. Creates a fresh LAContext when the session has expired.
  • .appLifecycle(timeoutInSeconds:): Similar to the session policy, but the session persists for the lifetime of the app process. Creates a fresh LAContext when the session has expired. The default timeout is 1 hour (3600 seconds).

Note

The .always, .session, and .appLifecycle policies create a new LAContext for each authentication attempt (when required), ensuring that the biometric prompt is shown reliably. The .default policy reuses the same context, which allows the system to optimize prompt frequency.

Examples:

// Default behavior - system manages biometric prompts (default)
credentialsManager.enableBiometrics(withTitle: "Unlock with Face ID",
                                    policy: .default)

// Always require biometric authentication with fresh prompt
credentialsManager.enableBiometrics(withTitle: "Unlock with Face ID",
                                    policy: .always)

// Require authentication only once per 5-minute session
credentialsManager.enableBiometrics(withTitle: "Unlock with Face ID",
                                    policy: .session(timeoutInSeconds: 300))

// Require authentication once per app lifecycle (1 hour default)
credentialsManager.enableBiometrics(withTitle: "Unlock with Face ID",
                                    policy: .appLifecycle()) // Default: 3600 seconds (1 hour)

Managing Biometric Sessions:

You can manually clear the biometric session to force re-authentication on the next credential access:

// Clear the biometric session
credentialsManager.clearBiometricSession()

// Check if the current session is valid
let isValid = credentialsManager.isBiometricSessionValid()

Note

Retrieving the user information with credentialsManager.userProfile() will not be protected by biometric authentication.

IPSIE session expiry [EA]

Note

This feature is currently available in Early Access. It requires session-expiry enforcement enabled on your OIDC or Okta enterprise connection in the Auth0 Dashboard.

Auth0 supports the IPSIE SL1 session_expiry claim, which lets an upstream identity provider (e.g. Okta) set a hard ceiling on how long an Auth0-issued session may live. When your connection has this option enabled, Auth0 includes a session_expiry Unix timestamp in the ID token it returns to your app after login.

The CredentialsManager enforces this ceiling on every retrieval — credentials(), ssoCredentials(), and apiCredentials(). Once the ceiling has passed (with a 30-second clock-skew leeway), the call clears the stored credentials and returns a CredentialsManagerError.sessionExpired error instead of attempting a token renewal. No code changes are needed to opt in — the enforcement is transparent once the connection option is active on your tenant.

The ceiling is pinned at the initial login: the session_expiry value from the first ID token is persisted to the Keychain and never overwritten by a refresh-token grant. This means a renewal whose ID token re-emits the claim cannot raise the ceiling past the original value. clear() removes the pinned value on logout so the next login pins a fresh ceiling.

What session_expiry means

ClaimBoundsSDK behaviour
expID token lifetime (minutes)Already validated on login
session_expiryRP session ceiling (hours / days)Enforced by CredentialsManager — returns .sessionExpired when reached

Handling the error

When session_expiry is reached, credentials() returns CredentialsManagerError.sessionExpired. Your existing "no credentials" re-login path already handles this — or you can match the case explicitly:

credentialsManager.credentials { result in
    switch result {
    case .success(let credentials):
        print("Obtained credentials: \(credentials)")
    case .failure(CredentialsManagerError.sessionExpired):
        // Upstream IdP session has ended — send the user back to login
        Auth0.webAuth().start { _ in }
    case .failure(let error):
        print("Failed with: \(error)")
    }
}
Using async/await
do {
    let credentials = try await credentialsManager.credentials()
    print("Obtained credentials: \(credentials)")
} catch CredentialsManagerError.sessionExpired {
    // Upstream IdP session has ended — send the user back to login
    let _ = try? await Auth0.webAuth().start()
} catch {
    print("Failed with: \(error)")
}

Note

Upgrading existing apps. Sessions stored before the session_expiry option was enabled on your connection carry no ceiling and behave exactly as before — .sessionExpired is never returned for them. Once the option is turned on, credentials() can return .sessionExpired for a user who is already logged in, as soon as their ceiling passes. Any code that assumed a stored session stays usable until the access token expires now has this additional failure path to handle. Treat it the same way you would CredentialsManagerError.noCredentials: clear the local session and redirect the user to log in again.

Reading the session_expiry value

Credentials exposes the ceiling via the sessionExpiresAt property (Date, or nil when the connection does not emit the claim), which you can use for app-level logic such as a countdown timer:

credentialsManager.credentials { result in
    guard case .success(let credentials) = result,
          let sessionExpiresAt = credentials.sessionExpiresAt else { return }
    print("Session ceiling: \(sessionExpiresAt)")
}

Note

sessionExpiresAt reflects the session_expiry claim in the current ID token only. The CredentialsManager enforces the ceiling pinned at the initial login, which may differ from — or be absent from — a later renewal token. For the authoritative ceiling value, rely on the CredentialsManager error, not this property.

Important

session_expiry is a ceiling computed at login time — it is not real-time session revocation. If a user is de-provisioned mid-session, they will not be immediately signed out; that requires back-channel logout / CAEP, which is a separate platform capability.

Go up ⤴

Other credentials

API credentials [EA]

Note

This feature is currently available in Early Access. Please reach out to Auth0 support to get it enabled for your tenant.

When the user logs in, you can request an access token for a specific API by passing its API identifier as the audience value. The access token in the resulting credentials can then be used to make authenticated requests to that API.

However, if you need an access token for a different API, you can exchange the refresh token for credentials containing an access token specific to this other API. This method is thread-safe.

Important

Currently, only the Auth0 My Account API is supported. Support for other APIs will be added in the future.

credentialsManager.apiCredentials(forAudience: "https://example.com/me",
                                  scope: "create:me:authentication_methods") { result in
    switch result {
    case .success(let apiCredentials):
        print("Obtained API credentials: \(apiCredentials)")
    case .failure(let error):
        print("Failed with: \(error)")
    }
}
Using async/await
do {
    let apiCredentials = try await credentialsManager.apiCredentials(forAudience: "https://example.com/me",
                                                                     scope: "create:me:authentication_methods")
    print("Obtained API credentials: \(apiCredentials)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
credentialsManager
    .apiCredentials(forAudience: "https://example.com/me",
                    scope: "create:me:authentication_methods")
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { apiCredentials in
        print("Obtained API credentials: \(apiCredentials)")
    })
    .store(in: &cancellables)

See Get a refresh token to learn how to obtain a refresh token.

Caution

To ensure that no concurrent exchange requests get made, do not call this method from multiple Credentials Manager instances. The Credentials Manager cannot synchronize requests across instances.

SSO credentials

Note


This feature is currently available in Early Access. Please reach out to Auth0 support to get it enabled for your tenant.

To implement single sign-on (SSO) with Universal Login, you can use either ASWebAuthenticationSession or SFSafariViewController as the in-app browser. Each has its own advantages and disadvantages, and suit different use cases.

An alternative way to implement SSO is by making use of a session transfer token. This is a single-use, short-lived token you must send to your website –either via query parameter or cookie– when opening it from your app. Your website then needs to redirect the user to Auth0's /authorize endpoint, passing along the session transfer token. Auth0 will set the respective session cookies and then redirect the user back to your website. Now, the user will be logged in on your website too. This solution will work with any browser and webview –even standalone browser apps.

First, you need to exchange the refresh token for a set of SSO credentials containing a session transfer token. This method is thread-safe.

credentialsManager.ssoCredentials { result in
    switch result {
    case .success(let ssoCredentials):
        print("Obtained SSO credentials: \(ssoCredentials)")
    case .failure(let error):
        print("Failed with: \(error)")
    }
}
Using async/await
do {
    let ssoCredentials = try await credentialsManager.ssoCredentials()
    print("Obtained SSO credentials: \(ssoCredentials)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
credentialsManager
    .ssoCredentials()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { ssoCredentials in
        print("Obtained SSO credentials: \(ssoCredentials)")
    })
    .store(in: &cancellables)

See Get a refresh token to learn how to obtain a refresh token.

Caution

To ensure that no concurrent exchange requests get made, do not call this method from multiple Credentials Manager instances. The Credentials Manager cannot synchronize requests across instances.

Then, when opening your website on any browser or web view, add the session transfer token to the URL as a query parameter. For example, https://example.com/login?session_transfer_token=THE_TOKEN.

If you're using WKWebView to open your website, you can place the session transfer token inside a cookie instead. It will be automatically sent to the /authorize endpoint.

let cookie = HTTPCookie(properties: [
    .domain: "YOUR_AUTH0_DOMAIN", // Or custom domain, if your website is using one
    .path: "/",
    .name: "auth0_session_transfer_token",
    .value: ssoCredentials.sessionTransferToken,
    .expires: ssoCredentials.expiresAt,
    .secure: true
])!

webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)

Important

Make sure the cookie's domain matches the Auth0 domain your website is using, regardless of the one your mobile app is using. Otherwise, the /authorize endpoint will not receive the cookie. If your website is using the default Auth0 domain (like example.us.auth0.com), set the cookie's domain to this value. On the other hand, if your website is using a custom domain, use this value instead.

Credentials Manager errors

The Credentials Manager will only produce CredentialsManagerError error values. You can find the underlying error (if any) in the cause: Error? property of the CredentialsManagerError. Not all error cases will have an underlying cause. Check the API documentation to learn more about the error cases you need to handle, and which ones include a cause value.

credentialsManager.credentials { result in
    switch result {
    case .success(let credentials):
        print("Obtained credentials: \(credentials)")
    case .failure(let error):
        switch error {
        case CredentialsManagerError.noCredentials:
            // No credentials stored — prompt login
            break
        case CredentialsManagerError.noRefreshToken:
            // Credentials expired and no refresh token — prompt login
            break
        case CredentialsManagerError.renewFailed:
            // Token renewal failed — check error.cause for details
            break
        case CredentialsManagerError.storeFailed:
            // Failed to save renewed credentials to Keychain
            break
        case CredentialsManagerError.clearFailed:
            // Failed to remove credentials from Keychain
            break
        case CredentialsManagerError.biometricsFailed:
            // Biometric authentication failed — check error.cause for details
            break
        case CredentialsManagerError.revokeFailed:
            // Token revocation failed — check error.cause for details
            break
        case CredentialsManagerError.sessionExpired:
            // Upstream IdP session ceiling reached — prompt re-login
            break
        default:
            break
        }
    }
}

To revoke the stored refresh token and clear credentials, use the revoke() method:

credentialsManager.revoke { result in
    switch result {
    case .success:
        // Refresh token revoked and credentials cleared
        break
    case .failure(let error):
        switch error {
        case CredentialsManagerError.noCredentials:
            // No credentials in storage — nothing to revoke
            break
        case CredentialsManagerError.revokeFailed:
            // Network revocation failed — the refresh token may still be active
            break
        case CredentialsManagerError.clearFailed:
            // Token was revoked but credentials could not be removed from storage
            break
        default:
            break
        }
    }
}

DPoP error handling

When using DPoP with the Credentials Manager, additional validation is performed on credential retrieval to ensure the DPoP key pair is consistent. The following errors may be returned:

  • dpopNotConfigured: The stored credentials are DPoP-bound but the Authentication client was not configured with DPoP via .useDPoP(). Ensure the Authentication client used by the CredentialsManager has DPoP enabled.

  • dpopKeyMissing: The DPoP key pair is no longer available in the Keychain (e.g., due to app reinstall or Keychain reset). Stored credentials are cleared automatically. The user must log in again.

  • dpopKeyMismatch: The current DPoP key pair does not match the one used when the credentials were saved. Stored credentials are cleared automatically. The user must log in again.

credentialsManager.credentials { result in
    switch result {
    case .success(let credentials):
        print("Obtained credentials: \(credentials)")
    case .failure(let error):
        switch error {
        case .dpopNotConfigured:
            // Authentication client was not configured with .useDPoP().
            // Fix the CredentialsManager initialisation:
            //   CredentialsManager(authentication: Auth0.authentication().useDPoP())
            break
        case .dpopKeyMissing:
            // DPoP key was lost (e.g. app reinstall). Prompt user to re-authenticate.
            break
        case .dpopKeyMismatch:
            // DPoP key doesn't match the one used at login. Prompt user to re-authenticate.
            break
        default:
            print("Failed with: \(error)")
        }
    }
}

Warning

Do not parse or otherwise rely on the error messages to handle the errors. The error messages are not part of the API and can change. Run a switch statement on the error cases instead, which are part of the API.

Go up ⤴

Authentication API (iOS / macOS / tvOS / watchOS / visionOS)

See all the available features in the API documentation ↗

Note

All completion callbacks in Auth0.swift execute on the main thread, making it safe to update UI directly. If needed, explicitly dispatch to a background thread.

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 in the settings page of your Auth0 application, as explained in Update Grant Types.

For login or signup with username/password, the Password grant type needs to be enabled in your Auth0 application. 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.

Note

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

Warning

The ID tokens obtained from Web Auth login are automatically validated by Auth0.swift, 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.

Log in with database connection

Auth0
    .authentication()
    .login(usernameOrEmail: "support@auth0.com",
           password: "secret-password",
           realmOrConnection: "Username-Password-Authentication",
           scope: "openid profile email offline_access")
    .start { result in
        switch result {
        case .success(let credentials):
            print("Obtained credentials: \(credentials)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let credentials = try await Auth0
        .authentication()
        .login(usernameOrEmail: "support@auth0.com",
               password: "secret-password",
               realmOrConnection: "Username-Password-Authentication",
               scope: "openid profile email offline_access")
        .start()
    print("Obtained credentials: \(credentials)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .authentication()
    .login(usernameOrEmail: "support@auth0.com",
           password: "secret-password",
           realmOrConnection: "Username-Password-Authentication",
           scope: "openid profile email offline_access")
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { credentials in
        print("Obtained credentials: \(credentials)")
    })
    .store(in: &cancellables)

Note

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

Sign up with database connection

The default connection is "Username-Password-Authentication".

Auth0
    .authentication()
    .signup(email: "support@auth0.com",
            password: "secret-password",
            userMetadata: ["first_name": "John", "last_name": "Appleseed"])
    .start { result in
        switch result {
        case .success(let user):
            print("User signed up: \(user)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }

Or specify a custom connection:

Auth0
    .authentication()
    .signup(email: "support@auth0.com",
            password: "secret-password",
            connection: "My-Custom-DB",
            userMetadata: ["first_name": "John", "last_name": "Appleseed"])
    .start { result in
        switch result {
        case .success(let user):
            print("User signed up: \(user)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }

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

Using async/await
do {
    let user = try await Auth0
        .authentication()
        .signup(email: "support@auth0.com",
                password: "secret-password",
                connection: "Username-Password-Authentication",
                userMetadata: ["first_name": "John", "last_name": "Appleseed"])
        .start()
    print("User signed up: \(user)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .authentication()
    .signup(email: "support@auth0.com",
            password: "secret-password",
            connection: "Username-Password-Authentication",
            userMetadata: ["first_name": "John", "last_name": "Appleseed"])
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { user in
        print("User signed up: \(user)")
    })
    .store(in: &cancellables)

Log in with passkey [EA]

Note

This feature is currently available in Early Access. Please reach out to Auth0 support to get it enabled for your tenant.

Logging a user in with a passkey is a three-step process. First, you request a login challenge from Auth0. Then, you pass that challenge to Apple's AuthenticationServices APIs to request an existing passkey credential. Finally, you use the resulting passkey credential and the original challenge to log the user in.

Prerequisites

  • A custom domain configured for your Auth0 tenant.
  • The Passkeys grant to be enabled for your Auth0 application.
  • The iOS Device Settings configured for your Auth0 application.

Check our documentation for more information.

1. Request a login challenge

If a database connection name is not specified, your tenant's default directory will be used.

Auth0
    .authentication()
    .passkeyLoginChallenge(connection: "Username-Password-Authentication")
    .start { result in
        switch result {
        case .success(let loginChallenge):
            print("Obtained login challenge: \(loginChallenge)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let loginChallenge = try await Auth0
        .authentication()
        .passkeyLoginChallenge(connection: "Username-Password-Authentication")
        .start()
    print("Obtained login challenge: \(loginChallenge)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .authentication()
    .passkeyLoginChallenge(connection: "Username-Password-Authentication")
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { loginChallenge in
        print("Obtained login challenge: \(loginChallenge)")
    })
    .store(in: &cancellables)

2. Request an existing passkey credential

Use the login challenge with ASAuthorizationPlatformPublicKeyCredentialProvider from the AuthenticationServices framework to request an existing passkey credential. Check out Supporting passkeys to learn more.

let credentialProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(
    relyingPartyIdentifier: loginChallenge.relyingPartyId
)

let request = credentialProvider.createCredentialAssertionRequest(
    challenge: loginChallenge.challengeData
)

let authController = ASAuthorizationController(authorizationRequests: [request])
authController.delegate = self // ASAuthorizationControllerDelegate
authController.presentationContextProvider = self
authController.performRequests()

The resulting passkey credential will be delivered through the ASAuthorizationControllerDelegate delegate.

func authorizationController(controller: ASAuthorizationController,
                             didCompleteWithAuthorization authorization: ASAuthorization) {
    switch authorization.credential {
    case let loginPasskey as ASAuthorizationPlatformPublicKeyCredentialAssertion:
        // ...
    default:
        print("Unrecognized credential: \(authorization.credential)")
    }

    // ...
}

3. Log the user in

Use the resulting passkey credential and the login challenge to log the user in.

Auth0
    .authentication()
    .login(passkey: loginPasskey,
           challenge: loginChallenge,
           connection: "Username-Password-Authentication",
           scope: "openid profile email offline_access")
    .start { result in
        switch result {
        case .success(let credentials):
            print("Obtained credentials: \(credentials)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let credentials = try await Auth0
        .authentication()
        .login(passkey: loginPasskey,
               challenge: loginChallenge,
               connection: "Username-Password-Authentication",
               scope: "openid profile email offline_access")
        .start()
    print("Obtained credentials: \(credentials)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .authentication()
    .login(passkey: loginPasskey,
           challenge: loginChallenge,
           connection: "Username-Password-Authentication",
           scope: "openid profile email offline_access")
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { credentials in
        print("Obtained credentials: \(credentials)")
    })
    .store(in: &cancellables)

Sign up with passkey [EA]

Note

This feature is currently available in Early Access. Please reach out to Auth0 support to get it enabled for your tenant.

Signing a user up with a passkey is a three-step process. First, you request a signup challenge from Auth0. Then, you pass that challenge to Apple's AuthenticationServices APIs to create a new passkey credential. Finally, you use the created passkey credential and the original challenge to log the new user in.

Prerequisites

  • A custom domain configured for your Auth0 tenant.
  • The Passkeys grant to be enabled for your Auth0 application.
  • The iOS Device Settings configured for your Auth0 application.

Check our documentation for more information.

1. Request a signup challenge

You need to provide at least one user identifier when requesting the challenge, along with an optional user display name, and an optional database connection name. If a connection name is not specified, your tenant's default directory will be used.

By default, database connections require a valid email. If you have enabled Flexible Identifiers for your database connection, you may use any combination of email, phoneNumber, or username. These user identifiers can be required or optional and must match your Flexible Identifiers configuration.

Auth0
    .authentication()
    .passkeySignupChallenge(email: "support@auth0.com",
                            name: "John Appleseed",
                            connection: "Username-Password-Authentication")
    .start { result in
        switch result {
        case .success(let signupChallenge):
            print("Obtained signup challenge: \(signupChallenge)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let signupChallenge = try await Auth0
        .authentication()
        .passkeySignupChallenge(email: "support@auth0.com",
                                name: "John Appleseed",
                                connection: "Username-Password-Authentication")
        .start()
    print("Obtained signup challenge: \(signupChallenge)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .authentication()
    .passkeySignupChallenge(email: "support@auth0.com",
                            name: "John Appleseed",
                            connection: "Username-Password-Authentication")
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { signupChallenge in
        print("Obtained signup challenge: \(signupChallenge)")
    })
    .store(in: &cancellables)

2. Create a new passkey credential

Use the signup challenge with ASAuthorizationPlatformPublicKeyCredentialProvider from the AuthenticationServices framework to generate a new passkey credential. Check out Supporting passkeys to learn more.

let credentialProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(
    relyingPartyIdentifier: signupChallenge.relyingPartyId
)

let request = credentialProvider.createCredentialRegistrationRequest(
    challenge: signupChallenge.challengeData,
    name: signupChallenge.userName,
    userID: signupChallenge.userId
)

let authController = ASAuthorizationController(authorizationRequests: [request])
authController.delegate = self // ASAuthorizationControllerDelegate
authController.presentationContextProvider = self
authController.performRequests()

The created passkey credential will be delivered through the ASAuthorizationControllerDelegate delegate.

func authorizationController(controller: ASAuthorizationController,
                             didCompleteWithAuthorization authorization: ASAuthorization) {
    switch authorization.credential {
    case let signupPasskey as ASAuthorizationPlatformPublicKeyCredentialRegistration:
        // ...
    default:
        print("Unrecognized credential: \(authorization.credential)")
    }

    // ...
}

3. Log the new user in

Use the created passkey credential and the signup challenge to log the new user in. This completes the signup process.

Auth0
    .authentication()
    .login(passkey: signupPasskey,
           challenge: signupChallenge,
           connection: "Username-Password-Authentication",
           scope: "openid profile email offline_access")
    .start { result in
        switch result {
        case .success(let credentials):
            print("Obtained credentials: \(credentials)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let credentials = try await Auth0
        .authentication()
        .login(passkey: signupPasskey,
               challenge: signupChallenge,
               connection: "Username-Password-Authentication",
               scope: "openid profile email offline_access")
        .start()
    print("Obtained credentials: \(credentials)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .authentication()
    .login(passkey: signupPasskey,
           challenge: signupChallenge,
           connection: "Username-Password-Authentication",
           scope: "openid profile email offline_access")
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { credentials in
        print("Obtained credentials: \(credentials)")
    })
    .store(in: &cancellables)

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.

Auth0
    .authentication()
    .startPasswordless(email: "support@auth0.com")
    .start { result in
        switch result {
        case .success:
            print("Code sent")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    try await Auth0
        .authentication()
        .startPasswordless(email: "support@auth0.com")
        .start()
    print("Code sent")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .authentication()
    .startPasswordless(email: "support@auth0.com")
    .start()
    .sink(receiveCompletion: { completion in
        switch completion {
        case .finished:
            print("Code sent")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }, receiveValue: {})
    .store(in: &cancellables)

Note

Use startPasswordless(phoneNumber:) to send a code to the user's phone number instead.

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.

Auth0
    .authentication()
    .login(email: "support@auth0.com", code: "123456")
    .start { result in
        switch result {
        case .success(let credentials):
            print("Obtained credentials: \(credentials)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let credentials = try await Auth0
        .authentication()
        .login(email: "support@auth0.com", code: "123456")
        .start()
    print("Obtained credentials: \(credentials)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .authentication()
    .login(email: "support@auth0.com", code: "123456")
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { credentials in
        print("Obtained credentials: \(credentials)")
    })
    .store(in: &cancellables)

Note

Use login(phoneNumber:code:) if the code was sent to the user's phone number.

Passwordless login with a database connection [EA]

Important

Passwordless login for database connections is currently in Early Access. Please reach out to Auth0 support to get it enabled for your tenant.

This flow lets users authenticate with a one-time code sent over email or SMS/voice against a database connection that has email_otp or phone_otp enabled. It is distinct from the /passwordless/start flow above, which uses dedicated passwordless connections.

1. Issue an OTP challenge

Send a one-time code to the user's email. For privacy, the server always responds successfully regardless of whether the user exists. On success, save the returned PasswordlessChallenge for step 2.

To send the code via SMS or voice instead, use passwordlessChallenge(phoneNumber:deliveryMethod:) against a connection with phone_otp enabled.

Both methods accept an optional allowSignup parameter (defaults to false) that controls whether a new user is created if one does not yet exist.

2. Verify the code and log in

Pass the PasswordlessChallenge from step 1 together with the code the user received to exchange for Credentials. If DPoP is enabled, a DPoP proof is attached automatically to this token request.

Step 1 — issue the challenge:

// Keep this reference until the user enters the code
var passwordlessChallenge: PasswordlessChallenge?

Auth0
    .authentication()
    .passwordlessChallenge(email: "user@example.com",
                           connection: "your-db-connection") // defaults to "Username-Password-Authentication"
    .start { result in
        switch result {
        case .success(let challenge):
            passwordlessChallenge = challenge
            // Prompt the user for the OTP they received
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }

Step 2 — verify the OTP:

guard let challenge = passwordlessChallenge else { return }

Auth0
    .authentication()
    .login(otp: userEnteredOTP, challenge: challenge)
    .start { result in
        switch result {
        case .success(let credentials):
            print("Obtained credentials: \(credentials)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    // Step 1: issue the challenge and keep it
    let challenge = try await Auth0
        .authentication()
        .passwordlessChallenge(email: "user@example.com",
                               connection: "your-db-connection") // defaults to "Username-Password-Authentication"
        .start()
    // Step 2: once the user enters the code, pass the saved challenge back to log in
    let credentials = try await Auth0
        .authentication()
        .login(otp: userEnteredOTP, challenge: challenge)
        .start()
    print("Obtained credentials: \(credentials)")
} catch {
    print("Failed with: \(error)")
}

Note

The default scope used is openid profile email. Regardless of the scopes set to the request, the openid scope is always enforced.

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.

Auth0
   .authentication()
   .userInfo(withAccessToken: credentials.accessToken)
   .start { result in
       switch result {
       case .success(let user):
           print("Obtained user: \(user)")
       case .failure(let error):
           print("Failed with: \(error)")
       }
   }
Using async/await
do {
    let user = try await Auth0
        .authentication()
        .userInfo(withAccessToken: credentials.accessToken)
        .start()
    print("Obtained user: \(user)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .authentication()
    .userInfo(withAccessToken: credentials.accessToken)
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { user in
        print("Obtained user: \(user)")
    })
    .store(in: &cancellables)

Renew credentials

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

See Get a refresh token to learn how to obtain a refresh token.

Auth0
    .authentication()
    .renew(withRefreshToken: credentials.refreshToken)
    .start { result in
        switch result {
        case .success(let credentials):
            print("Obtained new credentials: \(credentials)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let credentials = try await Auth0
        .authentication()
        .renew(withRefreshToken: credentials.refreshToken)
        .start()
    print("Obtained new credentials: \(credentials)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .authentication()
    .renew(withRefreshToken: credentials.refreshToken)
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { credentials in
        print("Obtained new credentials: \(credentials)")
    })
    .store(in: &cancellables)

Get SSO credentials

To implement single sign-on (SSO) with Universal Login, you can use either ASWebAuthenticationSession or SFSafariViewController as the in-app browser. Each has its own advantages and disadvantages, and suit different use cases.

An alternative way to implement SSO is by making use of a session transfer token. This is a one-use, short-lived token you must send to your website –either via query parameter or cookie– when opening it from your app. Your website then needs to redirect the user to Auth0's /authorize endpoint, passing along the session transfer token. Auth0 will set the respective session cookies and then redirect the user back to your website. Now, the user will be logged in on your website too. This solution will work with any browser and webview –even standalone browser apps.

First, you need to exchange the refresh token for a set of SSO credentials containing a session transfer token.

Auth0
    .authentication()
    .ssoExchange(withRefreshToken: credentials.refreshToken)
    .start { result in
        switch result {
        case .success(let ssoCredentials):
            print("Obtained SSO credentials: \(ssoCredentials)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let ssoCredentials = try await Auth0
        .authentication()
        .ssoExchange(withRefreshToken: credentials.refreshToken)
        .start()
    print("Obtained SSO credentials: \(ssoCredentials)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .authentication()
    .ssoExchange(withRefreshToken: credentials.refreshToken)
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { ssoCredentials in
        print("Obtained SSO credentials: \(ssoCredentials)")
    })
    .store(in: &cancellables)

See Get a refresh token to learn how to obtain a refresh token.

Important

You don't need to store the SSO credentials. The session transfer token is single-use and short-lived. However, if you're using refresh token rotation, you will get a new refresh token with the SSO credentials. You should store the new refresh token, replacing the previous one that is now invalid.

If you're using the Credentials Manager to store the user's credentials, you should use its ssoCredentials() method to perform the exchange. It will automatically handle the refresh tokens for you. And it's also thread-safe, whereas this method is not.

Then, when opening your website on any browser or web view, add the session transfer token to the URL as a query parameter. For example, https://example.com/login?session_transfer_token=THE_TOKEN.

If you're using WKWebView to open your website, you can place the session transfer token inside a cookie instead. It will be automatically sent to the /authorize endpoint.

let cookie = HTTPCookie(properties: [
    .domain: "YOUR_AUTH0_DOMAIN", // Or custom domain, if your website is using one
    .path: "/",
    .name: "auth0_session_transfer_token",
    .value: ssoCredentials.sessionTransferToken,
    .expires: ssoCredentials.expiresAt,
    .secure: true
])!

webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)

Important

Make sure the cookie's domain matches the Auth0 domain your website is using, regardless of the one your mobile app is using. Otherwise, the /authorize endpoint will not receive the cookie. If your website is using the default Auth0 domain (like example.us.auth0.com), set the cookie's domain to this value. On the other hand, if your website is using a custom domain, use this value instead.

DPoP

DPoP (Demonstrating Proof of Possession) is an application-level mechanism for sender-constraining OAuth 2.0 access and refresh tokens by proving that the app is in possession of a certain private key. You can enable it by calling the useDPoP() method. This ensures that DPoP proofs are generated for requests made through the Authentication API client.

let authenticationClient = Auth0.authentication().useDPoP()

Important

DPoP will only be used for new user sessions created after enabling it. DPoP will not be applied to any requests involving existing access and refresh tokens (such as exchanging the refresh token for new credentials).

This means that, after you've enabled it in your app, DPoP will only take effect when users log in again. It's up to you to decide how to roll out this change to your users. For example, you might require users to log in again the next time they open your app. You'll need to implement the logic to handle this transition based on your app's requirements.

When making requests to your own APIs, use the DPoP.addHeaders() method to add the Authorization and DPoP headers to a URLRequest. The Authorization header is set using the access token and token type, while the DPoP header contains the generated DPoP proof.

var request = URLRequest(url: URL(string: "https://example.com/api/endpoint")!)
request.httpMethod = "POST"

try DPoP.addHeaders(to: &request,
                    accessToken: credentials.accessToken,
                    tokenType: credentials.tokenType)

If your API is issuing DPoP nonces to prevent replay attacks, you can pass the nonce value to the addHeaders() method to include it in the DPoP proof. Use the DPoP.isNonceRequired(by:) method to check if a particular API response failed because a nonce is required.

if DPoP.isNonceRequired(by: response), 
    let nonce = response.value(forHTTPHeaderField: "DPoP-Nonce") {
    try DPoP.addHeaders(to: &request,
                        accessToken: credentials.accessToken,
                        tokenType: credentials.tokenType,
                        nonce: nonce)

    // Retry the request with the new DPoP proof that includes the nonce
}

On logout, you should call DPoP.clearKeypair() to delete the user's key pair from the Keychain.

try credentialsManager.clear()
try DPoP.clearKeypair()

Authentication API client configuration

Add custom parameters

Use the parameters() method to add custom parameters to any request.

Auth0
    .authentication()
    .renew(withRefreshToken: credentials.refreshToken) // Any request
    .parameters(["key": "value"])
    // ...

Add custom headers

Use the headers() method to add custom headers to any request.

Auth0
    .authentication()
    .renew(withRefreshToken: credentials.refreshToken) // Any request
    .headers(["key": "value"])
    // ...

Use a custom URLSession instance

You can specify a custom URLSession instance for more advanced networking configuration, such as customizing timeout values.

Auth0
    .authentication(session: customURLSession)
    // ...

Authentication API client errors

The Authentication API client will only produce AuthenticationError error values.

  • The info property contains additional information about the error.
  • The cause property contains the underlying error value, if any.
  • Use the isNetworkError property to check if the request failed due to networking issues.
  • Use the isRetryable property to check if the error represents a transient failure that can be retried (network errors, rate limiting, or server errors).

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

Warning

Do not parse or otherwise rely on the error messages to handle the errors. The error messages are not part of the API and can change. Use the error types instead, which are part of the API.

Go up ⤴

MFA API (iOS / macOS / tvOS / watchOS / visionOS)

See all the available features in the API documentation ↗

The MFA API allows you to implement multi-factor authentication flows using the Auth0 Authentication API. This includes enrolling MFA factors, challenging enrolled factors, and verifying MFA codes.

Note

The MFA API requires specific grant types to be enabled in your Auth0 application. Check the Dashboard under Application Settings > Advanced Settings > Grant Types.

Prerequisites

To use the MFA API, you need to:

  1. Enable the appropriate MFA grant type for your Auth0 application

  2. Enable the MFA factors you want to use in the Auth0 Dashboard under Security > Multi-factor Auth.

  3. For SMS, email, or push notification factors, configure the appropriate providers in your Auth0 tenant.

Handling MFA required errors

When a user attempts to log in and MFA is required, you'll receive an AuthenticationError with the isMultifactorRequired property set to true. This error contains an MFA token that you'll need for subsequent MFA operations.

The error payload includes two fields:

  • enroll: Available when the user needs to enroll a new MFA factor. Contains the types of factors they can enroll.
  • challenge: Available when the user has already enrolled MFA factors. Contains the types of factors available to challenge.
Auth0
    .authentication()
    .login(usernameOrEmail: "support@auth0.com",
           password: "secret-password",
           realmOrConnection: "Username-Password-Authentication",
           scope: "openid profile email")
    .start { result in
        switch result {
        case .success(let credentials):
            print("Obtained credentials: \(credentials)")
        case .failure(let error) where error.isMultifactorRequired:
            // MFA is required
            if let mfaPayload = error.mfaRequiredErrorPayload {
                let mfaToken = mfaPayload.mfaToken
                print("MFA token: \(mfaToken)")

                // Check if enrollment is required
                if let enrollTypes = mfaPayload.mfaRequirements.enroll {
                    print("User needs to enroll MFA")
                    print("Available enrollment types: \(enrollTypes.map { \$0.type })")
                    // Example output: ["otp", "phone", "push-notification"]
                    // Proceed with MFA enrollment using one of these types
                }

                // Check if challenge is available (user already enrolled)
                if let challengeTypes = mfaPayload.mfaRequirements.challenge {
                    print("User has enrolled MFA factors")
                    print("Available challenge types: \(challengeTypes.map { \$0.type })")
                    // Example output: ["otp", "phone"]
                    // Get authenticators and challenge one of them
                }
            }
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let credentials = try await Auth0
        .authentication()
        .login(usernameOrEmail: "support@auth0.com",
               password: "secret-password",
               realmOrConnection: "Username-Password-Authentication",
               scope: "openid profile email")
        .start()
    print("Obtained credentials: \(credentials)")
} catch let error as AuthenticationError where error.isMultifactorRequired {
    // MFA is required
    if let mfaPayload = error.mfaRequiredErrorPayload {
        let mfaToken = mfaPayload.mfaToken
        print("MFA token: \(mfaToken)")

        // Check if enrollment is required
        if let enrollTypes = mfaPayload.mfaRequirements.enroll {
            print("User needs to enroll MFA")
            print("Available enrollment types: \(enrollTypes.map { \$0.type })")
            // Example output: ["otp", "phone", "push-notification"]
        }

        // Check if challenge is available (user already enrolled)
        if let challengeTypes = mfaPayload.mfaRequirements.challenge {
            print("User has enrolled MFA factors")
            print("Available challenge types: \(challengeTypes.map { \$0.type })")
            // Example output: ["otp", "phone"]
        }
    }
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .authentication()
    .login(usernameOrEmail: "support@auth0.com",
           password: "secret-password",
           realmOrConnection: "Username-Password-Authentication",
           scope: "openid profile email")
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error as AuthenticationError) = completion,
           error.isMultifactorRequired,
           let mfaPayload = error.mfaRequiredErrorPayload {
            let mfaToken = mfaPayload.mfaToken
            print("MFA token: \(mfaToken)")

            if let enrollTypes = mfaPayload.mfaRequirements.enroll {
                print("User needs to enroll MFA")
                print("Available enrollment types: \(enrollTypes.map { \$0.type })")
                // Example output: ["otp", "phone", "push-notification"]
            }

            if let challengeTypes = mfaPayload.mfaRequirements.challenge {
                print("User has enrolled MFA factors")
                print("Available challenge types: \(challengeTypes.map { \$0.type })")
                // Example output: ["otp", "phone"]
            }
        }
    }, receiveValue: { credentials in
        print("Obtained credentials: \(credentials)")
    })
    .store(in: &cancellables)

Get available authenticators

After receiving an MFA token, you can retrieve the list of available authenticators that the user has already enrolled. Use the factors from the challenge field of the MFA required error payload to filter the authenticators.

// Extract factors from the challenge field of MFA required error payload
let factorsAllowed = mfaPayload.mfaRequirements.challenge?.map { \$0.type } ?? []

Auth0
    .mfa()
    .getAuthenticators(mfaToken: mfaToken, factorsAllowed: factorsAllowed)
    .start { result in
        switch result {
        case .success(let authenticators):
            print("Available authenticators: \(authenticators)")
            for authenticator in authenticators {
                print("ID: \(authenticator.id), Type: \(authenticator.authenticatorType)")
            }
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
// Extract factors from the challenge field of MFA required error payload
let factorsAllowed = mfaPayload.mfaRequirements.challenge?.map { \$0.type } ?? []

do {
    let authenticators = try await Auth0
        .mfa()
        .getAuthenticators(mfaToken: mfaToken, factorsAllowed: factorsAllowed)
        .start()
    print("Available authenticators: \(authenticators)")
    for authenticator in authenticators {
        print("ID: \(authenticator.id), Type: \(authenticator.authenticatorType)")
    }
} catch {
    print("Failed with: \(error)")
}
Using Combine
// Extract factors from the challenge field of MFA required error payload
let factorsAllowed = mfaPayload.mfaRequirements.challenge?.map { \$0.type } ?? []

Auth0
    .mfa()
    .getAuthenticators(mfaToken: mfaToken, factorsAllowed: factorsAllowed)
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { authenticators in
        print("Available authenticators: \(authenticators)")
        for authenticator in authenticators {
            print("ID: \(authenticator.id), Type: \(authenticator.authenticatorType)")
        }
    })
    .store(in: &cancellables)

Enroll MFA factors

When MFA enrollment is required, you can enroll various types of MFA factors.

Enroll SMS

Enroll a phone number for SMS-based MFA. An SMS with a verification code will be sent to the phone number.

Auth0
    .mfa()
    .enroll(mfaToken: mfaToken, phoneNumber: "+12025550135")
    .start { result in
        switch result {
        case .success(let challenge):
            print("SMS enrollment initiated")
            print("OOB Code: \(challenge.oobCode)")
            // Now prompt user for the code they received via SMS
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let challenge = try await Auth0
        .mfa()
        .enroll(mfaToken: mfaToken, phoneNumber: "+12025550135")
        .start()
    print("SMS enrollment initiated")
    print("OOB Code: \(challenge.oobCode)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .mfa()
    .enroll(mfaToken: mfaToken, phoneNumber: "+12025550135")
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { challenge in
        print("SMS enrollment initiated")
        print("OOB Code: \(challenge.oobCode)")
    })
    .store(in: &cancellables)

Enroll email

Enroll an email address for email-based MFA. A verification code will be sent to the email address.

Auth0
    .mfa()
    .enroll(mfaToken: mfaToken, email: "user@example.com")
    .start { result in
        switch result {
        case .success(let challenge):
            print("Email enrollment initiated")
            print("OOB Code: \(challenge.oobCode)")
            // Now prompt user for the code they received via email
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let challenge = try await Auth0
        .mfa()
        .enroll(mfaToken: mfaToken, email: "user@example.com")
        .start()
    print("Email enrollment initiated")
    print("OOB Code: \(challenge.oobCode)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .mfa()
    .enroll(mfaToken: mfaToken, email: "user@example.com")
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { challenge in
        print("Email enrollment initiated")
        print("OOB Code: \(challenge.oobCode)")
    })
    .store(in: &cancellables)

Enroll OTP (TOTP)

Enroll a time-based one-time password (TOTP) authenticator. This returns a QR code and secret that can be scanned by authenticator apps like Google Authenticator or Authy.

Auth0
    .mfa()
    .enroll(mfaToken: mfaToken)
    .start { result in
        switch result {
        case .success(let challenge):
            print("OTP enrollment initiated")
            if let barcodeUri = challenge.barcodeUri {
                print("QR Code URI: \(barcodeUri)")
                // Display this as a QR code for the user to scan
            }
            if let secret = challenge.secret {
                print("Secret: \(secret)")
                // User can manually enter this into their authenticator app
            }
            // After user scans QR code and sets up authenticator app,
            // prompt them for the OTP code to complete enrollment
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let challenge = try await Auth0
        .mfa()
        .enroll(mfaToken: mfaToken)
        .start()
    print("OTP enrollment initiated")
    if let barcodeUri = challenge.barcodeUri {
        print("QR Code URI: \(barcodeUri)")
    }
    if let secret = challenge.secret {
        print("Secret: \(secret)")
    }
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .mfa()
    .enroll(mfaToken: mfaToken)
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { challenge in
        print("OTP enrollment initiated")
        if let barcodeUri = challenge.barcodeUri {
            print("QR Code URI: \(barcodeUri)")
        }
        if let secret = challenge.secret {
            print("Secret: \(secret)")
        }
    })
    .store(in: &cancellables)

Enroll push notification

Enroll Auth0 Guardian push notifications as an MFA factor.

Auth0
    .mfa()
    .enroll(mfaToken: mfaToken)
    .start { result in
        switch result {
        case .success(let challenge):
            print("Push notification enrollment initiated")
            if let barcodeUri = challenge.barcodeUri {
                print("QR Code URI: \(barcodeUri)")
                // Display this as a QR code for the user to scan with Guardian app
            }
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let challenge = try await Auth0
        .mfa()
        .enroll(mfaToken: mfaToken)
        .start()
    print("Push notification enrollment initiated")
    if let barcodeUri = challenge.barcodeUri {
        print("QR Code URI: \(barcodeUri)")
    }
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .mfa()
    .enroll(mfaToken: mfaToken)
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { challenge in
        print("Push notification enrollment initiated")
        if let barcodeUri = challenge.barcodeUri {
            print("QR Code URI: \(barcodeUri)")
        }
    })
    .store(in: &cancellables)

Challenge an enrolled authenticator

For already enrolled MFA factors, you can request a challenge to be sent to the user.

Auth0
    .mfa()
    .challenge(with: "sms|dev_authenticator_id", mfaToken: mfaToken)
    .start { result in
        switch result {
        case .success(let challenge):
            print("Challenge sent")
            print("Challenge type: \(challenge.challengeType)")
            if let oobCode = challenge.oobCode {
                print("OOB Code: \(oobCode)")
            }
            // Now prompt user for the verification code they received
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let challenge = try await Auth0
        .mfa()
        .challenge(with: "sms|dev_authenticator_id", mfaToken: mfaToken)
        .start()
    print("Challenge sent")
    print("Challenge type: \(challenge.challengeType)")
    if let oobCode = challenge.oobCode {
        print("OOB Code: \(oobCode)")
    }
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .mfa()
    .challenge(with: "sms|dev_authenticator_id", mfaToken: mfaToken)
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { challenge in
        print("Challenge sent")
        print("Challenge type: \(challenge.challengeType)")
        if let oobCode = challenge.oobCode {
            print("OOB Code: \(oobCode)")
        }
    })
    .store(in: &cancellables)

Verify MFA

After enrolling or challenging an MFA factor, you need to verify the code to complete authentication.

Verify with OOB code

Verify an out-of-band (OOB) code received via SMS or email.

Auth0
    .mfa()
    .verify(oobCode: "123456", bindingCode: nil, mfaToken: mfaToken)
    .start { result in
        switch result {
        case .success(let credentials):
            print("MFA verification successful!")
            print("Obtained credentials: \(credentials)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }

With a binding code for enhanced security:

Auth0
    .mfa()
    .verify(oobCode: "oob_code_value", bindingCode: "BINDING_CODE", mfaToken: mfaToken)
    .start { result in
        switch result {
        case .success(let credentials):
            print("MFA verification successful!")
            print("Obtained credentials: \(credentials)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let credentials = try await Auth0
        .mfa()
        .verify(oobCode: "123456", bindingCode: nil, mfaToken: mfaToken)
        .start()
    print("MFA verification successful!")
    print("Obtained credentials: \(credentials)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .mfa()
    .verify(oobCode: "123456", bindingCode: nil, mfaToken: mfaToken)
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { credentials in
        print("MFA verification successful!")
        print("Obtained credentials: \(credentials)")
    })
    .store(in: &cancellables)

Verify with OTP code

Verify a one-time password (OTP) code from an authenticator app.

Auth0
    .mfa()
    .verify(otp: "123456", mfaToken: mfaToken)
    .start { result in
        switch result {
        case .success(let credentials):
            print("MFA verification successful!")
            print("Obtained credentials: \(credentials)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let credentials = try await Auth0
        .mfa()
        .verify(otp: "123456", mfaToken: mfaToken)
        .start()
    print("MFA verification successful!")
    print("Obtained credentials: \(credentials)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .mfa()
    .verify(otp: "123456", mfaToken: mfaToken)
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { credentials in
        print("MFA verification successful!")
        print("Obtained credentials: \(credentials)")
    })
    .store(in: &cancellables)

Verify with recovery code

Verify using a recovery code when the primary MFA factor is unavailable.

Auth0
    .mfa()
    .verify(recoveryCode: "RECOVERY_CODE_123", mfaToken: mfaToken)
    .start { result in
        switch result {
        case .success(let credentials):
            print("MFA verification successful!")
            print("Obtained credentials: \(credentials)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let credentials = try await Auth0
        .mfa()
        .verify(recoveryCode: "RECOVERY_CODE_123", mfaToken: mfaToken)
        .start()
    print("MFA verification successful!")
    print("Obtained credentials: \(credentials)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .mfa()
    .verify(recoveryCode: "RECOVERY_CODE_123", mfaToken: mfaToken)
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { credentials in
        print("MFA verification successful!")
        print("Obtained credentials: \(credentials)")
    })
    .store(in: &cancellables)

Complete MFA flow examples

Complete SMS enrollment and verification flow

// Step 1: Initial login attempt
Auth0
    .authentication()
    .login(usernameOrEmail: email, password: password, realmOrConnection: "Username-Password-Authentication")
    .start { result in
        switch result {
        case .success(let credentials):
            print("Login successful: \(credentials)")

        case .failure(let error) where error.isMultifactorRequired:
            guard let mfaToken = error.mfaRequiredErrorPayload?.mfaToken else { return }

            // Step 2: Enroll SMS
            Auth0
                .mfa()
                .enroll(mfaToken: mfaToken, phoneNumber: "+12025550135")
                .start { enrollResult in
                    switch enrollResult {
                    case .success(let challenge):
                        print("SMS sent with OOB code: \(challenge.oobCode)")

                        // Step 3: User enters the code they received via SMS
                        let userEnteredCode = "123456" // Get this from user input

                        // Step 4: Verify the OOB code
                        Auth0
                            .mfa()
                            .verify(oobCode: userEnteredCode, bindingCode: nil, mfaToken: mfaToken)
                            .start { verifyResult in
                                switch verifyResult {
                                case .success(let credentials):
                                    print("MFA enrollment complete! Credentials: \(credentials)")
                                case .failure(let error):
                                    print("Verification failed: \(error)")
                                }
                            }

                    case .failure(let error):
                        print("Enrollment failed: \(error)")
                    }
                }

        case .failure(let error):
            print("Login failed: \(error)")
        }
    }

Complete OTP enrollment and verification flow

// Step 1: Initial login attempt
Auth0
    .authentication()
    .login(usernameOrEmail: email, password: password, realmOrConnection: "Username-Password-Authentication")
    .start { result in
        switch result {
        case .success(let credentials):
            print("Login successful: \(credentials)")

        case .failure(let error) where error.isMultifactorRequired:
            guard let mfaToken = error.mfaRequiredErrorPayload?.mfaToken else { return }

            // Step 2: Enroll OTP authenticator
            Auth0
                .mfa()
                .enroll(mfaToken: mfaToken)
                .start { enrollResult in
                    switch enrollResult {
                    case .success(let challenge):
                        // Step 3: Display QR code to user
                        if let barcodeUri = challenge.barcodeUri {
                            print("Show this QR code to user: \(barcodeUri)")
                            // Generate and display QR code from this URI
                        }
                        if let secret = challenge.secret {
                            print("Or manual entry code: \(secret)")
                        }

                        // Step 4: User scans QR code and enters OTP from their app
                        let userEnteredOtp = "123456" // Get this from user input

                        // Step 5: Verify the OTP code
                        Auth0
                            .mfa()
                            .verify(otp: userEnteredOtp, mfaToken: mfaToken)
                            .start { verifyResult in
                                switch verifyResult {
                                case .success(let credentials):
                                    print("MFA enrollment complete! Credentials: \(credentials)")
                                case .failure(let error):
                                    print("Verification failed: \(error)")
                                }
                            }

                    case .failure(let error):
                        print("Enrollment failed: \(error)")
                    }
                }

        case .failure(let error):
            print("Login failed: \(error)")
        }
    }

Challenge existing MFA factor flow

// Step 1: Initial login attempt
Auth0
    .authentication()
    .login(usernameOrEmail: email, password: password, realmOrConnection: "Username-Password-Authentication")
    .start { result in
        switch result {
        case .success(let credentials):
            print("Login successful: \(credentials)")

        case .failure(let error) where error.isMultifactorRequired:
            guard let mfaPayload = error.mfaRequiredErrorPayload else { return }
            let mfaToken = mfaPayload.mfaToken

            // Step 2: Extract factors from the challenge field
            let factorsAllowed = mfaPayload.mfaRequirements.challenge?.map { \$0.type } ?? []

            // Step 3: Get available authenticators
            Auth0
                .mfa()
                .getAuthenticators(mfaToken: mfaToken, factorsAllowed: factorsAllowed)
                .start { authResult in
                    switch authResult {
                    case .success(let authenticators):
                        guard let authenticator = authenticators.first else { return }

                        // Step 4: Challenge the authenticator
                        Auth0
                            .mfa()
                            .challenge(with: authenticator.id, mfaToken: mfaToken)
                            .start { challengeResult in
                                switch challengeResult {
                                case .success(let challenge):
                                    print("Challenge sent: \(challenge)")

                                    // Step 5: User enters the code
                                    let userCode = "123456" // Get from user input

                                    // Step 6: Verify based on challenge type
                                    if challenge.challengeType == "oob" {
                                        Auth0
                                            .mfa()
                                            .verify(oobCode: userCode, bindingCode: nil, mfaToken: mfaToken)
                                            .start { print(\$0) }
                                    } else if challenge.challengeType == "otp" {
                                        Auth0
                                            .mfa()
                                            .verify(otp: userCode, mfaToken: mfaToken)
                                            .start { print(\$0) }
                                    }

                                case .failure(let error):
                                    print("Challenge failed: \(error)")
                                }
                            }

                    case .failure(let error):
                        print("Failed to get authenticators: \(error)")
                    }
                }

        case .failure(let error):
            print("Login failed: \(error)")
        }
    }

MFA client configuration

Add custom parameters

Use the parameters() method to add custom parameters to any request.

Auth0
    .mfa()
    .verify(otp: "123456", mfaToken: mfaToken) // Any request
    .parameters(["key": "value"])
    // ...

Add custom headers

Use the headers() method to add custom headers to any request.

Auth0
    .mfa()
    .verify(otp: "123456", mfaToken: mfaToken) // Any request
    .headers(["key": "value"])
    // ...

Use a custom URLSession instance

You can specify a custom URLSession instance for more advanced networking configuration, such as customizing timeout values.

Auth0
    .mfa(session: customURLSession)
    // ...

MFA client errors

The MFA client produces specific error types for different operations, all conforming to the Auth0APIError protocol:

  • MfaListAuthenticatorsError: Returned by getAuthenticators() when listing authenticators fails
  • MfaEnrollmentError: Returned by all enroll() methods when enrollment fails
  • MfaChallengeError: Returned by challenge() when initiating a challenge fails
  • MFAVerifyError: Returned by all verify() methods when verification fails

All MFA error types provide:

  • code: The error code from the API response
  • statusCode: The HTTP status code
  • info: Raw error information dictionary
  • localizedDescription: A human-readable error description (inherited from LocalizedError)
  • debugDescription: A detailed description for debugging purposes
  • cause: The underlying Error value, if any (useful for network errors)
  • isNetworkError: Whether the request failed due to network issues
  • isRetryable: Whether the error is retryable (network errors, rate limiting, or server errors)

Example error handling

Auth0
    .mfa()
    .verify(otp: "123456", mfaToken: mfaToken)
    .start { result in
        switch result {
        case .success(let credentials):
            print("Success: \(credentials)")
        case .failure(let error):
            print("Failed with code: \(error.code)")
            print("Description: \(error.localizedDescription)")
            print("Status code: \(error.statusCode)")
        }
    }

Common error codes

Each MFA error type provides specific error codes to help you handle different scenarios:

MfaListAuthenticatorsError (from getAuthenticators()):

  • invalid_request: Request parameters are invalid (e.g., missing or empty factorsAllowed)
  • invalid_token: MFA token is invalid or expired
  • access_denied: User lacks permission to access this resource

MfaEnrollmentError (from enroll() methods):

  • invalid_request: Enrollment parameters are invalid
  • invalid_token: MFA token is invalid or expired
  • enrollment_conflict: Authenticator is already enrolled
  • unsupported_challenge_type: Requested factor type is not enabled

MfaChallengeError (from challenge()):

  • invalid_request: Challenge parameters are invalid
  • invalid_token: MFA token is invalid or expired
  • authenticator_not_found: Specified authenticator doesn't exist
  • unsupported_challenge_type: Authenticator type doesn't support challenges

MFAVerifyError (from verify() methods):

  • invalid_grant: Verification code is incorrect or expired
  • invalid_token: MFA token is invalid or expired
  • invalid_oob_code: Out-of-band code is invalid
  • invalid_binding_code: Binding code (SMS/email code) is incorrect
  • expired_token: Verification code has expired

Handling specific error cases

You can check the code property to handle specific error scenarios:

Auth0
    .mfa()
    .enroll(mfaToken: mfaToken, phoneNumber: "+12025550135")
    .start { result in
        switch result {
        case .success(let challenge):
            print("Enrollment successful")
        case .failure(let error):
            switch error.code {
            case "invalid_token":
                print("MFA token is invalid or expired")
            case "invalid_phone_number":
                print("Phone number format is invalid")
            case "unsupported_challenge_type":
                print("This MFA factor is not supported")
            default:
                print("Enrollment failed: \(error.localizedDescription)")
            }
        }
    }

Network and retryable errors

MFA errors inherit isNetworkError and isRetryable properties from Auth0APIError to help handle transient failures:

Auth0
    .mfa()
    .verify(otp: "123456", mfaToken: mfaToken)
    .start { result in
        switch result {
        case .success(let credentials):
            print("Success: \(credentials)")
        case .failure(let error):
            if error.isNetworkError {
                print("Network connectivity issue - check your connection")
            } else if error.isRetryable {
                print("Request can be retried (rate limiting or server error)")
            } else {
                print("Permanent error: \(error.localizedDescription)")
            }
        }
    }

The isNetworkError property returns true for network-related failures such as:

  • No internet connection
  • DNS lookup failures
  • Connection timeouts
  • Data not allowed

The isRetryable property returns true for errors that can be retried:

  • Network errors (as determined by isNetworkError)
  • Rate limiting errors (HTTP 429)
  • Server errors (HTTP 5xx)

Authentication flow errors

When handling MFA-required errors from the authentication flow (not the MFA client), you'll still receive AuthenticationError values. Use these properties to identify MFA-related scenarios:

  • isMultifactorRequired: MFA is required to authenticate
  • isMultifactorEnrollRequired: MFA is required and the user is not enrolled
  • isMultifactorCodeInvalid: The MFA code sent is invalid or expired (legacy)
  • isMultifactorTokenInvalid: The MFA token is invalid or expired (legacy)
Auth0
    .authentication()
    .login(usernameOrEmail: "user@example.com", password: "password", realmOrConnection: "Username-Password-Authentication")
    .start { result in
        switch result {
        case .success(let credentials):
            print("Success: \(credentials)")
        case .failure(let error) where error.isMultifactorRequired:
            print("MFA is required")
            // Extract mfaToken and proceed with MFA flow
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }

Check the Auth0APIError API documentation and AuthenticationError API documentation to learn more about error handling.

Warning

Do not parse or otherwise rely on the error messages to handle the errors. The error messages are not part of the API and can change. Use the error code property and error types instead, which are part of the API.

Go up ⤴

My Account API (iOS / macOS / tvOS / watchOS / visionOS) [EA]

See all the available features in the API documentation ↗

Note

The My Account API is currently available in Early Access. Please reach out to Auth0 support to get it enabled for your tenant.

Use the Auth0 My Account API to manage the current user's account.

To call the My Account API, you need an access token issued specifically for this API, including any required scopes for the operations you want to perform. See API credentials [EA] to learn how to obtain one.

Enroll a new passkey

Scopes required: create:me:authentication_methods

Enrolling a new passkey is a three-step process. First, you request an enrollment challenge from Auth0. Then, you pass that challenge to Apple's AuthenticationServices APIs to create a new passkey credential. Finally, you use the created passkey credential and the original challenge to enroll the passkey with Auth0.

Prerequisites

  • A custom domain configured for your Auth0 tenant.
  • The Passkeys grant to be enabled for your Auth0 application.
  • The iOS Device Settings configured for your Auth0 application.

Check our documentation for more information.

1. Request an enrollment challenge

You can specify an optional user identity identifier and/or a database connection name to help Auth0 find the user. The user identity identifier will be needed if the user logged in with a linked account.

Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .passkeyEnrollmentChallenge()
    .start { result in
        switch result {
        case .success(let enrollmentChallenge):
            print("Obtained enrollment challenge: \(enrollmentChallenge)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let enrollmentChallenge = try await Auth0
        .myAccount(token: apiCredentials.accessToken)
        .authenticationMethods
        .passkeyEnrollmentChallenge()
        .start()
    print("Obtained enrollment challenge: \(enrollmentChallenge)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .passkeyEnrollmentChallenge()
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { enrollmentChallenge in
        print("Obtained enrollment challenge: \(enrollmentChallenge)")
    })
    .store(in: &cancellables)

2. Create a new passkey credential

Use the enrollment challenge with ASAuthorizationPlatformPublicKeyCredentialProvider from the AuthenticationServices framework to generate a new passkey credential. Check out Supporting passkeys to learn more.

let credentialProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(
    relyingPartyIdentifier: enrollmentChallenge.relyingPartyId
)

let request = credentialProvider.createCredentialRegistrationRequest(
    challenge: enrollmentChallenge.challengeData,
    name: enrollmentChallenge.userName,
    userID: enrollmentChallenge.userId
)

let authController = ASAuthorizationController(authorizationRequests: [request])
authController.delegate = self // ASAuthorizationControllerDelegate
authController.presentationContextProvider = self
authController.performRequests()

The created passkey credential will be delivered through the ASAuthorizationControllerDelegate delegate.

func authorizationController(controller: ASAuthorizationController,
                             didCompleteWithAuthorization authorization: ASAuthorization) {
    switch authorization.credential {
    case let newPasskey as ASAuthorizationPlatformPublicKeyCredentialRegistration:
        // ...
    default:
        print("Unrecognized credential: \(authorization.credential)")
    }

    // ...
}

3. Enroll the passkey

Use the created passkey credential and the enrollment challenge to enroll the passkey with Auth0.

Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .enroll(passkey: newPasskey,
            challenge: enrollmentChallenge)
    .start { result in
        switch result {
        case .success(let authenticationMethod):
            print("Enrolled passkey: \(authenticationMethod)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let authenticationMethod = try await Auth0
        .myAccount(token: apiCredentials.accessToken)
        .authenticationMethods
        .enroll(passkey: newPasskey,
                challenge: enrollmentChallenge)
        .start()
    print("Enrolled passkey: \(authenticationMethod)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .enroll(passkey: newPasskey,
            challenge: enrollmentChallenge)
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { authenticationMethod in
        print("Enrolled passkey: \(authenticationMethod)")
    })
    .store(in: &cancellables)

Enroll a new email authentication method

Scopes required: create:me:authentication_methods

Enrolling a new email authentication method is a two-step process. First, you request an enrollment challenge from Auth0. Then, you use the original challenge to enroll the email authentication method with Auth0.

Prerequisites

  • Enable the MFA grant type for your application. Go to Auth0 Dashboard > Applications > Advanced Settings > Grant Types and select MFA.
  • Enable the Email factor. Go to Auth0 Dashboard > Security > Multi-factor Auth > Email.
  • The iOS Device Settings configured for your Auth0 application.

1. Request an enrollment challenge

Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .enrollEmail(emailAddress: emailAddress)
    .start { result in
        switch result {
        case .success(let enrollmentChallenge):
            print("Obtained enrollment challenge: \(enrollmentChallenge)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let enrollmentChallenge = try await Auth0
        .myAccount(token: apiCredentials.accessToken)
        .authenticationMethods
        .enrollEmail(emailAddress: emailAddress)
        .start()
    print("Obtained enrollment challenge: \(enrollmentChallenge)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .enrollEmail(emailAddress: emailAddress)
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { enrollmentChallenge in
        print("Obtained enrollment challenge: \(enrollmentChallenge)")
    })
    .store(in: &cancellables)

2. Enroll the email authentication method

Use the otpCode received in the email used for generating enrollment challenge, authSession and id from enrollment challenge to enroll the email with Auth0.

Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .confirmEmailEnrollment(id: id,
                           authSession: authSession,
                           otpCode: otpCode)
    .start { result in
        switch result {
        case .success(let authenticationMethod):
            print("Enrolled email: \(authenticationMethod)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let authenticationMethod = try await Auth0
        .myAccount(token: apiCredentials.accessToken)
        .authenticationMethods
        .confirmEmailEnrollment(id: id,
                                authSession: authSession,
                                otpCode: otpCode)
        .start()
    print("Enrolled email: \(authenticationMethod)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .confirmEmailEnrollment(id: id,
                            authSession: authSession,
                            otpCode: otpCode)
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { authenticationMethod in
        print("Enrolled email: \(authenticationMethod)")
    })
    .store(in: &cancellables)

Enroll a new phone authentication method

Scopes required: create:me:authentication_methods

Enrolling a new phone authentication method is a two-step process. First, you request an enrollment challenge from Auth0. Then, you use the original challenge to enroll the phone authentication method with Auth0.

Prerequisites

  • Enable the MFA grant type for your application. Go to Auth0 Dashboard > Applications > Advanced Settings > Grant Types and select MFA.
  • Enable the Phone Message factor. Go to Auth0 Dashboard > Security > Multi-factor Auth > Phone Message.
  • The iOS Device Settings configured for your Auth0 application.

1. Request an enrollment challenge

Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .enrollPhone(phoneNumber: phoneNumber, 
                 preferredAuthenticationMethod: .sms)
    .start { result in
        switch result {
        case .success(let enrollmentChallenge):
            print("Obtained enrollment challenge: \(enrollmentChallenge)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let enrollmentChallenge = try await Auth0
        .myAccount(token: apiCredentials.accessToken)
        .authenticationMethods
        .enrollPhone(phoneNumber: phoneNumber, 
                     preferredAuthenticationMethod: .sms)
        .start()
    print("Obtained enrollment challenge: \(enrollmentChallenge)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .enrollPhone(phoneNumber: phoneNumber, 
                 preferredAuthenticationMethod: .sms)
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { enrollmentChallenge in
        print("Obtained enrollment challenge: \(enrollmentChallenge)")
    })
    .store(in: &cancellables)

2. Enroll the phone authentication method

Use the otpCode received in the phone number used for generating challenge, authSession and id from enrollment challenge to enroll the phone with Auth0.

Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .confirmPhoneEnrollment(id: id,
                           authSession: authSession,
                           otpCode: otpCode)
    .start { result in
        switch result {
        case .success(let authenticationMethod):
            print("Enrolled phone: \(authenticationMethod)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let authenticationMethod = try await Auth0
        .myAccount(token: apiCredentials.accessToken)
        .authenticationMethods
        .confirmPhoneEnrollment(id: id,
                               authSession: authSession,
                               otpCode: otpCode)
        .start()
    print("Enrolled phone: \(authenticationMethod)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .confirmPhoneEnrollment(id: id,
                           authSession: authSession,
                           otpCode: otpCode)
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { authenticationMethod in
        print("Enrolled phone: \(authenticationMethod)")
    })
    .store(in: &cancellables)

Enroll a new TOTP authentication method

Scopes required: create:me:authentication_methods

Enrolling a new TOTP authentication method is a two-step process. First, you request an enrollment challenge from Auth0. Then, you use the original challenge to enroll the TOTP authentication method with Auth0.

Prerequisites

  • Enable the MFA grant type for your application. Go to Auth0 Dashboard > Applications > Advanced Settings > Grant Types and select MFA.
  • Enable the One-time Password factor. Go to Auth0 Dashboard > Security > Multi-factor Auth > One-time Password.
  • The iOS Device Settings configured for your Auth0 application.

1. Request an enrollment challenge

Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .enrollTOTP()
    .start { result in
        switch result {
        case .success(let enrollmentChallenge):
            print("Obtained enrollment challenge: \(enrollmentChallenge)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let enrollmentChallenge = try await Auth0
        .myAccount(token: apiCredentials.accessToken)
        .authenticationMethods
        .enrollTOTP()
        .start()
    print("Obtained enrollment challenge: \(enrollmentChallenge)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .enrollTOTP()
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { enrollmentChallenge in
        print("Obtained enrollment challenge: \(enrollmentChallenge)")
    })
    .store(in: &cancellables)

2. Enroll the TOTP authentication method

Use the otpCode from the authenticator app, authSession and id from enrollment challenge to enroll the TOTP with Auth0.

Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .confirmTOTPEnrollment(id: id, 
                           authSession: authSession, 
                           otpCode: otpCode)
    .start { result in
        switch result {
        case .success(let authenticationMethod):
            print("Enrolled TOTP: \(authenticationMethod)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let authenticationMethod = try await Auth0
        .myAccount(token: apiCredentials.accessToken)
        .authenticationMethods
        .confirmTOTPEnrollment(id: id, 
                               authSession: authSession, 
                               otpCode: otpCode)
        .start()
    print("Enrolled TOTP: \(authenticationMethod)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
   .confirmTOTPEnrollment(id: id, 
                          authSession: authSession, 
                          otpCode: otpCode)
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { authenticationMethod in
        print("Enrolled TOTP: \(authenticationMethod)")
    })
    .store(in: &cancellables)

Enroll a new push notification authentication method

Scopes required: create:me:authentication_methods

Enrolling a new push notification authentication method is a two-step process. First, you request an enrollment challenge from Auth0. Then, you use the original challenge to enroll the push notification authentication method with Auth0.

Prerequisites

  • Enable the MFA grant type for your application. Go to Auth0 Dashboard > Applications > Advanced Settings > Grant Types and select MFA.
  • Enable the Email factor. Go to Auth0 Dashboard > Security > Multi-factor Auth > Push Notification using Auth0 Guardian.
  • The iOS Device Settings configured for your Auth0 application.

1. Request an enrollment challenge

Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .enrollPushNotification()
    .start { result in
        switch result {
        case .success(let enrollmentChallenge):
            print("Obtained enrollment challenge: \(enrollmentChallenge)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let enrollmentChallenge = try await Auth0
        .myAccount(token: apiCredentials.accessToken)
        .authenticationMethods
        .enrollPushNotification()
        .start()
    print("Obtained enrollment challenge: \(enrollmentChallenge)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .enrollPushNotification()
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { enrollmentChallenge in
        print("Obtained enrollment challenge: \(enrollmentChallenge)")
    })
    .store(in: &cancellables)

2. Enroll the push notification authentication method

To confirm the enrollment, the end user will need to scan a QR code with the barcode_uri from enrollment challenge in the Guardian application, within the next 5 minutes and invoke confirmPushNotificatonEnrollment method.

Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .confirmPushNotificationEnrollment(id: id, 
                                       authSession: authSession)
    .start { result in
        switch result {
        case .success(let authenticationMethod):
            print("Enrolled push notification: \(authenticationMethod)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let authenticationMethod = try await Auth0
        .myAccount(token: apiCredentials.accessToken)
        .authenticationMethods
        .confirmPushNotificationEnrollment(id: id, 
                                           authSession: authSession)
        .start()
    print("Enrolled push notification: \(authenticationMethod)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
   .confirmPushNotificationEnrollment(id: id, 
                                      authSession: authSession)
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { authenticationMethod in
        print("Enrolled push notification: \(authenticationMethod)")
    })
    .store(in: &cancellables)

Enroll a new recovery code authentication method

Scopes required: create:me:authentication_methods

Enrolling a new recovery code authentication method is a two-step process. First, you request an enrollment challenge from Auth0. Then, you use the original challenge to enroll the recovery code authentication method with Auth0.

Prerequisites

  • Enable the MFA grant type for your application. Go to Auth0 Dashboard > Applications > Advanced Settings > Grant Types and select MFA.
  • Enable the Email factor. Go to Auth0 Dashboard > Security > Multi-factor Auth > Recovery Code.
  • The iOS Device Settings configured for your Auth0 application.

1. Request an enrollment challenge

Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .enrollRecoveryCode()
    .start { result in
        switch result {
        case .success(let enrollmentChallenge):
            print("Obtained enrollment challenge: \(enrollmentChallenge)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let enrollmentChallenge = try await Auth0
        .myAccount(token: apiCredentials.accessToken)
        .authenticationMethods
        .enrollRecoveryCode()
        .start()
    print("Obtained enrollment challenge: \(enrollmentChallenge)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .enrollRecoveryCode()
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { enrollmentChallenge in
        print("Obtained enrollment challenge: \(enrollmentChallenge)")
    })
    .store(in: &cancellables)

2. Enroll the recovery code authentication method

Use the authSession and id from enrollment challenge to enroll the recovery code with Auth0.

Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .confirmRecoveryCodeEnrollment(id: id,
                                   authSession: authSession)
    .start { result in
        switch result {
        case .success(let authenticationMethod):
            print("Enrolled recovery code: \(authenticationMethod)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let authenticationMethod = try await Auth0
        .myAccount(token: apiCredentials.accessToken)
        .authenticationMethods
        .confirmRecoveryCodeEnrollment(id: id,
                                       authSession: authSession)
        .start()
    print("Enrolled Recovery code: \(authenticationMethod)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .confirmRecoveryCodeEnrollment(id: id,
                                   authSession: authSession)
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { authenticationMethod in
        print("Enrolled recovery code: \(authenticationMethod)")
    })
    .store(in: &cancellables)

Enroll a new password authentication method

Scopes required: create:me:authentication_methods

Enrolling a new password authentication method is a two-step process. First, you request an enrollment challenge, which returns the connection's password policy so you can guide the user to choose a compliant password. Then, you confirm the enrollment with the new password.

1. Request an enrollment challenge

You can specify an optional user identity identifier and/or a database connection name to help Auth0 find the user. The user identity identifier will be needed if the user logged in with a linked account.

Note

Pass userIdentityId without the identity provider prefix.

Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .enrollPassword()
    .start { result in
        switch result {
        case .success(let enrollmentChallenge):
            print("Obtained enrollment challenge: \(enrollmentChallenge)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let enrollmentChallenge = try await Auth0
        .myAccount(token: apiCredentials.accessToken)
        .authenticationMethods
        .enrollPassword()
        .start()
    print("Obtained enrollment challenge: \(enrollmentChallenge)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .enrollPassword()
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { enrollmentChallenge in
        print("Obtained enrollment challenge: \(enrollmentChallenge)")
    })
    .store(in: &cancellables)

2. Confirm the enrollment

Use the policy from the enrollment challenge to guide the user toward a compliant password, then use the authenticationId and authenticationSession to confirm the enrollment with the new password.

Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .confirmPasswordEnrollment(id: id,
                               authSession: authSession,
                               newPassword: newPassword)
    .start { result in
        switch result {
        case .success(let authenticationMethod):
            print("Enrolled password: \(authenticationMethod)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let authenticationMethod = try await Auth0
        .myAccount(token: apiCredentials.accessToken)
        .authenticationMethods
        .confirmPasswordEnrollment(id: id,
                                   authSession: authSession,
                                   newPassword: newPassword)
        .start()
    print("Enrolled password: \(authenticationMethod)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .confirmPasswordEnrollment(id: id,
                               authSession: authSession,
                               newPassword: newPassword)
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { authenticationMethod in
        print("Enrolled password: \(authenticationMethod)")
    })
    .store(in: &cancellables)

Get all factors

Scopes required: read:me:factors

Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .getFactors()
    .start { result in
        switch result {
        case .success(let factors):
            print("Obtained factors: \(factors)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let factors = try await Auth0
        .myAccount(token: apiCredentials.accessToken)
        .authenticationMethods
        .getFactors()
        .start()
    print("Obtained factors: \(factors)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .getFactors()
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { factors in
        print("Obtained factors: \(factors)")
    })
    .store(in: &cancellables)

Get all authentication methods

Scopes required: read:me:authentication_methods

Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .getAuthenticationMethods()
    .start { result in
        switch result {
        case .success(let authenticationMethods):
            print("Obtained authentication methods: \(authenticationMethods)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let authenticationMethods = try await Auth0
        .myAccount(token: apiCredentials.accessToken)
        .authenticationMethods
        .getAuthenticationMethods()
        .start()
    print("Obtained authentication methods: \(authenticationMethods)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .getAuthenticationMethods()
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { authenticationMethods in
        print("Obtained authentication methods: \(authenticationMethods)")
    })
    .store(in: &cancellables)

Get an authentication method by id

Scopes required: read:me:authentication_methods

Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .getAuthenticationMethod(by: id)
    .start { result in
        switch result {
        case .success(let authenticationMethod):
            print("Obtained authentication method: \(authenticationMethod)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let authenticationMethod = try await Auth0
        .myAccount(token: apiCredentials.accessToken)
        .authenticationMethods
        .getAuthenticationMethod(by: id)
        .start()
    print("Obtained authentication method: \(authenticationMethod)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .getAuthenticationMethod(by: id)
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { authenticationMethod in
        print("Obtained authentication method: \(authenticationMethod)")

    })
    .store(in: &cancellables)

Delete an authentication method

Scopes required: delete:me:authentication_methods

Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .deleteAuthenticationMethod(by: id)
    .start { result in
        switch result {
        case .success:
            print("Authentication method is successfully deleted")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let _ = try await Auth0
        .myAccount(token: apiCredentials.accessToken)
        .authenticationMethods
        .deleteAuthenticationMethod(by: id)
        .start()
    print("Authentication method is successfully deleted")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .myAccount(token: apiCredentials.accessToken)
    .authenticationMethods
    .deleteAuthenticationMethod(by: id)
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { _ in
        print("Authentication method is successfully deleted")
    })
    .store(in: &cancellables)

My Account API client errors

The My Account API client will only produce MyAccountError error values.

  • The info property contains additional information about the error.
  • The cause property contains the underlying error value, if any.
  • Use the isNetworkError property to check if the request failed due to networking issues.
  • Use the isRetryable property to check if the error represents a transient failure that can be retried (network errors, rate limiting, or server errors).

See the API documentation to learn more about the available MyAccountError properties.

Go up ⤴

Logging

Auth0.swift provides comprehensive logging capabilities for debugging HTTP requests and responses. The logging system is built on Apple's Unified Logging (OSLog) for better performance and integration with system diagnostic tools.

Enable Logging

Enable logging by calling the logging(enabled:) method on any Auth0.swift client that conforms to Loggable (e.g. Authentication, MFAClient, MyAccountClient, or WebAuth):

Auth0
    .webAuth()
    .logging(enabled: true)
    // ...
Auth0
    .authentication()
    .logging(enabled: true)
    // ...

Caution

Enable logging only when debugging to avoid performance impacts and potential security concerns in production builds.

Automatic Token Redaction

Security First: Auth0.swift automatically redacts sensitive information from logs to protect user credentials. The following fields are redacted when logging HTTP responses:

  • access_token
  • refresh_token
  • id_token

Redacted tokens appear as "redacted" in the logs, ensuring sensitive data never appears in plain text.

Logging Output

With logging enabled, you'll see detailed HTTP request and response information. Here's an example of what a successful authentication flow looks like:

ASWebAuthenticationSession: https://example.us.auth0.com/authorize?.....
Callback URL: com.example.MyApp://example.us.auth0.com/ios/com.example.MyApp/callback?...

POST https://example.us.auth0.com/oauth/token HTTP/1.1
Content-Type: application/json
Auth0-Client: eyJ2ZXJzaW9uI...

{
  "code": "...",
  "client_id": "...",
  "grant_type": "authorization_code",
  "redirect_uri": "com.example.MyApp://example.us.auth0.com/ios/com.example.MyApp/callback",
  "code_verifier": "..."
}

HTTP/1.1 200
Pragma: no-cache
Content-Type: application/json
Strict-Transport-Security: max-age=3600
Date: Wed, 08 Dec 2025 10:30:00 GMT
Content-Length: 1024
Cache-Control: no-cache
Connection: keep-alive

{
  "access_token": "redacted",
  "refresh_token": "redacted",
  "id_token": "redacted",
  "token_type": "Bearer",
  "expires_in": 86400
}

Viewing Logs

Auth0.swift logs are written to the Unified Logging System with the following identifiers:

  • Subsystem: com.auth0.Auth0
  • Categories: NetworkTracing, Configuration

Logs appear automatically in the Xcode debug console during development on all platforms.

Filtering in Xcode 15+:

Use these filter expressions directly in the console search bar:

FilterDescription
subsystem:com.auth0.Auth0Show all Auth0 SDK logs
category:NetworkTracingShow only network requests/responses
category:ConfigurationShow only configuration errors
subsystem:com.auth0.Auth0 category:NetworkTracingCombine filters for specific logs

Log Categories

  • NetworkTracing - HTTP requests and responses (enabled via logging(enabled: true))
  • Configuration - SDK setup and configuration issues (always logged)

Tip

When troubleshooting, you can also check the logs in the Auth0 Dashboard for more information.

Go up ⤴

Advanced Features

Native social login

Sign in With Apple

If you've added the Sign In with Apple flow to your app, after a successful Sign in With Apple authentication you can use the value of the authorizationCode property to perform a code exchange for Auth0 credentials.

Auth0
    .authentication()
    .login(appleAuthorizationCode: "auth-code")
    .start { result in
        switch result {
        case .success(let credentials):
            print("Obtained credentials: \(credentials)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let credentials = try await Auth0
        .authentication()
        .login(appleAuthorizationCode: "auth-code")
        .start()
    print("Obtained credentials: \(credentials)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .authentication()
    .login(appleAuthorizationCode: "auth-code")
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { credentials in
        print("Obtained credentials: \(credentials)")
    })
    .store(in: &cancellables)

Note

See the Setting up Sign In with Apple guide for more information about integrating Sign In with Apple with Auth0.

Facebook Login

If you've added the Facebook Login flow to your app, after a successful Facebook authentication you can request a session info access token and the Facebook user profile, and then use them both to perform a token exchange for Auth0 credentials.

Auth0
    .authentication()
    .login(facebookSessionAccessToken: "session-info-access-token",
           profile: ["key": "value"])
    .start { result in
        switch result {
        case .success(let credentials):
            print("Obtained credentials: \(credentials)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let credentials = try await Auth0
        .authentication()
        .login(facebookSessionAccessToken: "session-info-access-token",
               profile: ["key": "value"])
        .start()
    print("Obtained credentials: \(credentials)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .authentication()
    .login(facebookSessionAccessToken: "session-info-access-token",
           profile: ["key": "value"])
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { credentials in
        print("Obtained credentials: \(credentials)")
    })
    .store(in: &cancellables)

Note

See the Setting up Facebook Login guide for more information about integrating Facebook Login with Auth0.

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:

  • Get Auth0 tokens for another audience
  • Integrate an external identity provider
  • Migrate to Auth0
  • Delegation and impersonation (using actor tokens)

Note

This feature is currently available in Early Access. Please reach out to Auth0 support to get it enabled for your tenant.

Exchange to obtain Auth0 credentials using an existing identity provider token

Auth0
    .authentication()
    .customTokenExchange(subjectToken: "existing-token",
                         subjectTokenType: "urn:ietf:params:oauth:token-type:jwt",
                         audience: "https://example.com/api",
                         scope: "openid profile email",
                         organization: "org_id")
    .start { result in
        switch result {
        case .success(let credentials):
            print("Obtained credentials: \(credentials)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let credentials = try await Auth0
        .authentication()
        .customTokenExchange(subjectToken: "existing-token",
                            subjectTokenType: "urn:ietf:params:oauth:token-type:jwt",
                            audience: "https://example.com/api",
                            scope: "openid profile email",
                            organization: "org_id")
        .start()
    print("Obtained credentials: \(credentials)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .authentication()
    .customTokenExchange(subjectToken: "existing-token",
                         subjectTokenType: "urn:ietf:params:oauth:token-type:jwt",
                         audience: "https://example.com/api",
                         scope: "openid profile email",
                         organization: "org_id")
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { credentials in
        print("Obtained credentials: \(credentials)")
    })
    .store(in: &cancellables)

Delegation and impersonation with actor tokens

You can perform a token exchange with an actor token to support delegation and impersonation flows (per RFC 8693). The actor token identifies the party that is acting on behalf of the subject.

Use the ActorToken struct to bundle the actor token and its type together:

let actor = ActorToken(token: "actor-id-token",
                       tokenType: "urn:ietf:params:oauth:token-type:id_token")

Auth0
    .authentication()
    .customTokenExchange(subjectToken: "subject-token",
                         subjectTokenType: "urn:ietf:params:oauth:token-type:id_token",
                         actorToken: actor)
    .start { result in
        switch result {
        case .success(let credentials):
            print("Obtained credentials: \(credentials)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
let actor = ActorToken(token: "actor-id-token",
                       tokenType: "urn:ietf:params:oauth:token-type:id_token")

do {
    let credentials = try await Auth0
        .authentication()
        .customTokenExchange(subjectToken: "subject-token",
                             subjectTokenType: "urn:ietf:params:oauth:token-type:id_token",
                             actorToken: actor)
        .start()
    print("Obtained credentials: \(credentials)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
let actor = ActorToken(token: "actor-id-token",
                       tokenType: "urn:ietf:params:oauth:token-type:id_token")

Auth0
    .authentication()
    .customTokenExchange(subjectToken: "subject-token",
                         subjectTokenType: "urn:ietf:params:oauth:token-type:id_token",
                         actorToken: actor)
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { credentials in
        print("Obtained credentials: \(credentials)")
    })
    .store(in: &cancellables)

Note

When an actor token is provided, Auth0 will not issue a refresh token regardless of whether offline_access is in the scope.

Reading the act claim from the ID token

When a token exchange involves an actor token, Auth0 may include an act (actor) claim in the resulting ID token. This claim identifies who is acting on behalf of the subject and may be nested to represent delegation chains.

Note

The act claim is set server-side via an Auth0 Action that calls api.authentication.setActor(). Without this Action configured, the ID token will not contain an act claim even when an actor token is provided in the request.

The act claim is exposed through the UserInfo type via the ActClaim class. This example uses JWTDecode.swift to decode the ID token — add it to your project if you haven't already:

import JWTDecode

let jwt = try decode(jwt: credentials.idToken)
let userInfo = UserInfo(json: jwt.body)

if let act = userInfo?.act {
    print("Actor: \(act.sub)")
    print("Additional claims: \(act.additionalClaims)")

    // Check for delegation chain
    if let innerAct = act.act {
        print("Original actor: \(innerAct.sub)")
    }
}

The ActClaim class provides:

  • sub: The subject identifier of the acting party.
  • act: A nested ActClaim for delegation chains (e.g., act.act for multi-hop delegation).
  • additionalClaims: Any extra claims beyond sub and act (e.g., org, role).

You can also access the act claim from the Credentials Manager's stored user information:

if let act = credentialsManager.user?.act {
    print("Actor: \(act.sub)")
}

Organizations

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

Note

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

Log in to an organization

Auth0
    .webAuth()
    .organization("YOUR_AUTH0_ORGANIZATION_NAME_OR_ID")
    .start { result in
        switch result {
        case .success(let credentials):
            print("Obtained credentials: \(credentials)")
        case .failure(let error):
            print("Failed with: \(error)")
        }
    }
Using async/await
do {
    let credentials = try await Auth0
        .webAuth()
        .organization("YOUR_AUTH0_ORGANIZATION_NAME_OR_ID")
        .start()
    print("Obtained credentials: \(credentials)")
} catch {
    print("Failed with: \(error)")
}
Using Combine
Auth0
    .webAuth()
    .organization("YOUR_AUTH0_ORGANIZATION_NAME_OR_ID")
    .start()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { credentials in
        print("Obtained credentials: \(credentials)")
    })
    .store(in: &cancellables)

Accept user invitations

To accept organization invitations your app needs to support Universal Links, 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 invitationURL().

guard let url = URLContexts.first?.url else { return }

// You need to wait for the app to enter the foreground before launching Web Auth
NotificationCenter.default
    .publisher(for: UIApplication.didBecomeActiveNotification)
    .subscribe(on: DispatchQueue.main)
    .prefix(1)
    .setFailureType(to: WebAuthError.self) // Necessary for iOS 13
    .flatMap { _ in
        Auth0
            .webAuth()
            .invitationURL(url) // 👈🏼
            .start()
    }
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Failed with: \(error)")
        }
    }, receiveValue: { credentials in
        print("Obtained credentials: \(credentials)")
    })
    .store(in: &cancellables)

Bot Detection

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.

Auth0
    .authentication()
    .login(usernameOrEmail: email, 
           password: password, 
           realmOrConnection: connection, 
           scope: scope)
    .start { result in
        switch result {
        case .success(let credentials): // ...
        case .failure(let error) where error.isVerificationRequired:
            DispatchQueue.main.async {
                Auth0
                    .webAuth()
                    .useHTTPS() // Use a Universal Link callback URL on iOS 17.4+ / macOS 14.4+
                    .connection(connection)
                    .scope(scope)
                    .useEphemeralSession() // Otherwise a session cookie will remain
                    .parameters(["login_hint": email]) // So the user doesn't have to type it again
                    .start { result in
                        // ...
                    }
            }
        case .failure(let error): // ...
        }
    }

In the case of signup, you can add an additional parameter to make the user land directly on the signup page.

Auth0
    .webAuth()
    .parameters(["login_hint": email, "screen_hint": "signup"])
    // ...

Check how to set up Web Auth in the Web Auth Configuration section.