Examples
July 8, 2026 · View on GitHub
- Web Auth (iOS / macOS / visionOS)
- Credentials Manager (iOS / macOS / TVOS / watchOS / visionOS)
- Authentication API (iOS / macOS / TVOS / watchOS / visionOS)
- MFA API (iOS / macOS / tvOS / watchOS / visionOS)
- My Account API (iOS / macOS / tvOS / watchOS / visionOS) [EA]
- Logging
- Advanced Features
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
- Web Auth configuration
- ID token validation
- DPoP
- Automatic credentials management
- Web Auth errors
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.
| Parameters | No existing session | Existing session |
|---|---|---|
| No extra parameters | Shows the login page | Redirects to the callback URL |
"screen_hint": "signup" | Shows the signup page | Redirects to the callback URL |
"prompt": "login" | Shows the login page | Shows the login page |
"prompt": "login", "screen_hint": "signup" | Shows the signup page | Shows the signup page |
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.

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":
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)
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.
- Store credentials
- Check for stored credentials
- Retrieve stored credentials
- Renew stored credentials
- Retrieve stored user information
- Clear stored credentials
- Biometric authentication
- IPSIE session expiry [EA]
- Other credentials
- Credentials Manager errors
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:
- Request A calls
credentials()and starts a token refresh - Request A successfully hits the server and gets new credentials
- Request A fails on the way back (network issue), never reaching the client
- 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 sameLAContextinstance, 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 freshLAContextfor 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 freshLAContextwhen the session has expired..appLifecycle(timeoutInSeconds:): Similar to the session policy, but the session persists for the lifetime of the app process. Creates a freshLAContextwhen 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
| Claim | Bounds | SDK behaviour |
|---|---|---|
exp | ID token lifetime (minutes) | Already validated on login |
session_expiry | RP 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.
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 theAuthenticationclient was not configured with DPoP via.useDPoP(). Ensure theAuthenticationclient used by theCredentialsManagerhas 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.
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.
- Log in with database connection
- Sign up with database connection
- Log in with passkey [EA]
- Sign up with passkey [EA]
- Passwordless login
- Passwordless login with a database connection [EA]
- Retrieve user information
- Renew credentials
- Get SSO credentials
- DPoP
- Authentication API client configuration
- Authentication API client errors
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
infoproperty contains additional information about the error. - The
causeproperty contains the underlying error value, if any. - Use the
isNetworkErrorproperty to check if the request failed due to networking issues. - Use the
isRetryableproperty 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.
MFA API (iOS / macOS / tvOS / watchOS / visionOS)
See all the available features in the API documentation ↗
- Handling MFA required errors
- Get available authenticators
- Enroll MFA factors
- Challenge an enrolled authenticator
- Verify MFA
- Complete MFA flow examples
- MFA client configuration
- MFA client errors
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:
-
Enable the appropriate MFA grant type for your Auth0 application
-
Enable the MFA factors you want to use in the Auth0 Dashboard under Security > Multi-factor Auth.
-
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 bygetAuthenticators()when listing authenticators failsMfaEnrollmentError: Returned by allenroll()methods when enrollment failsMfaChallengeError: Returned bychallenge()when initiating a challenge failsMFAVerifyError: Returned by allverify()methods when verification fails
All MFA error types provide:
code: The error code from the API responsestatusCode: The HTTP status codeinfo: Raw error information dictionarylocalizedDescription: A human-readable error description (inherited fromLocalizedError)debugDescription: A detailed description for debugging purposescause: The underlyingErrorvalue, if any (useful for network errors)isNetworkError: Whether the request failed due to network issuesisRetryable: 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 expiredaccess_denied: User lacks permission to access this resource
MfaEnrollmentError (from enroll() methods):
invalid_request: Enrollment parameters are invalidinvalid_token: MFA token is invalid or expiredenrollment_conflict: Authenticator is already enrolledunsupported_challenge_type: Requested factor type is not enabled
MfaChallengeError (from challenge()):
invalid_request: Challenge parameters are invalidinvalid_token: MFA token is invalid or expiredauthenticator_not_found: Specified authenticator doesn't existunsupported_challenge_type: Authenticator type doesn't support challenges
MFAVerifyError (from verify() methods):
invalid_grant: Verification code is incorrect or expiredinvalid_token: MFA token is invalid or expiredinvalid_oob_code: Out-of-band code is invalidinvalid_binding_code: Binding code (SMS/email code) is incorrectexpired_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 authenticateisMultifactorEnrollRequired: MFA is required and the user is not enrolledisMultifactorCodeInvalid: 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.
My Account API (iOS / macOS / tvOS / watchOS / visionOS) [EA]
See all the available features in the API documentation ↗
- Enroll a new passkey
- Enroll a new email
- Enroll a new phone
- Enroll a new totp
- Enroll a new push notification
- Enroll a new recovery code
- Enroll a new password
- Get all factors
- Get all authentication methods
- Get an authentication method by id
- Delete an authentication method
- My Account API client errors
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
infoproperty contains additional information about the error. - The
causeproperty contains the underlying error value, if any. - Use the
isNetworkErrorproperty to check if the request failed due to networking issues. - Use the
isRetryableproperty 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.
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_tokenrefresh_tokenid_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
Xcode Console (Recommended)
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:
| Filter | Description |
|---|---|
subsystem:com.auth0.Auth0 | Show all Auth0 SDK logs |
category:NetworkTracing | Show only network requests/responses |
category:Configuration | Show only configuration errors |
subsystem:com.auth0.Auth0 category:NetworkTracing | Combine 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.
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 nestedActClaimfor delegation chains (e.g.,act.actfor multi-hop delegation).additionalClaims: Any extra claims beyondsubandact(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.