Skip to content
Skip
3k

Auth0

Auth0 authentication for Skip apps on both iOS and Android.

On iOS this wraps the Auth0.swift SDK (v2.18+). On Android, the Swift code is transpiled to Kotlin via Skip Lite and wraps the Auth0.Android SDK (v3.14).

Add the dependency to your Package.swift file:

let package = Package(
name: "my-package",
products: [
.library(name: "MyProduct", targets: ["MyTarget"]),
],
dependencies: [
.package(url: "https://source.skip.dev/skip-auth0.git", "0.0.0"..<"2.0.0"),
],
targets: [
.target(name: "MyTarget", dependencies: [
.product(name: "SkipAuth0", package: "skip-auth0")
])
]
)
  1. Create an Auth0 account and tenant.
  2. Create a Native application in your Auth0 dashboard.
  3. Note your Domain and Client ID.
  4. Configure Allowed Callback URLs and Allowed Logout URLs for both platforms (see below).
  5. If you plan to use loginWithCredentials or signUp, enable the Password grant type in your application’s advanced settings under Grant Types.

Add your Auth0 domain and client ID to your Info.plist, and register the callback URL scheme:

<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>YOUR_BUNDLE_ID</string>
</array>
</dict>
</array>

In your Auth0 dashboard, add the callback URL: YOUR_BUNDLE_ID://YOUR_AUTH0_DOMAIN/ios/YOUR_BUNDLE_ID/callback

Add your Auth0 domain and scheme to your AndroidManifest.xml via Gradle manifest placeholders. In the test target’s Skip/skip.yml this is configured as:

build:
contents:
- block: 'android'
contents:
- block: 'defaultConfig'
contents:
- 'manifestPlaceholders["auth0Domain"] = "YOUR_AUTH0_DOMAIN"'
- 'manifestPlaceholders["auth0Scheme"] = "YOUR_SCHEME"'

In your Auth0 dashboard, add the callback URL: YOUR_SCHEME://YOUR_AUTH0_DOMAIN/android/YOUR_PACKAGE_NAME/callback

See the Auth0 Android quickstart for details.

Configure the SDK early in your app’s lifecycle:

import SkipAuth0
@main struct MyApp: App {
init() {
Auth0SDK.shared.configure(Auth0Config(
domain: "yourapp.us.auth0.com",
clientId: "your-client-id",
scheme: "com.example.myapp" // Your URL scheme
))
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

The recommended approach is Auth0’s Universal Login, which opens the system browser:

Auth0SDK.shared.login { result in
switch result {
case .success(let credentials):
print("Logged in!")
print("Access token: \(credentials.accessToken ?? "")")
print("ID token: \(credentials.idToken ?? "")")
print("Refresh token: \(credentials.refreshToken ?? "")")
print("Expires at: \(credentials.expiresAt?.description ?? "")")
case .failure(let error):
print("Login failed: \(error)")
}
}

With custom scopes and audience:

Auth0SDK.shared.login(
scope: "openid profile email offline_access",
audience: "https://api.example.com"
) { result in
// Handle result...
}

For direct login without opening a browser (requires enabling the “Password” grant type in your Auth0 application):

Auth0SDK.shared.loginWithCredentials(
email: "user@example.com",
password: "securepassword"
) { result in
switch result {
case .success(let credentials):
print("Logged in with credentials")
case .failure(let error):
print("Login failed: \(error)")
}
}

Create a new user account and immediately log in to obtain tokens:

Auth0SDK.shared.signUp(
email: "newuser@example.com",
password: "securepassword"
) { result in
switch result {
case .success(let credentials):
print("Signed up and logged in!")
print("Access token: \(credentials.accessToken ?? "")")
case .failure(let error):
print("Sign up failed: \(error)")
}
}

With additional options:

Auth0SDK.shared.signUp(
email: "newuser@example.com",
password: "securepassword",
username: "newuser", // If your connection requires usernames
connection: "Username-Password-Authentication",
scope: "openid profile email offline_access",
audience: "https://api.example.com"
) { result in
// Handle result...
}

Prerequisites: The “Password” grant type must be enabled in your Auth0 application settings. The database connection must allow sign-ups (check Disable Sign Ups is unchecked in your connection settings).

Create a new user without logging in. Useful when an admin creates accounts or when you want to handle login separately:

Auth0SDK.shared.createUser(
email: "newuser@example.com",
password: "securepassword"
) { result in
switch result {
case .success(let user):
print("User created: \(user.email)")
print("Username: \(user.username ?? "none")")
print("Email verified: \(user.emailVerified)")
case .failure(let error):
print("User creation failed: \(error)")
}
}

Send a password reset email to the user:

Auth0SDK.shared.resetPassword(email: "user@example.com") { result in
switch result {
case .success:
print("Password reset email sent")
case .failure(let error):
print("Password reset failed: \(error)")
}
}

Note: For security reasons, this method will not fail if the email address does not exist in the database. The user will simply not receive an email.

Fetch the user’s profile from the Auth0 /userinfo endpoint:

Auth0SDK.shared.userInfo(accessToken: credentials.accessToken!) { result in
switch result {
case .success(let profile):
print("User ID: \(profile.sub)")
print("Name: \(profile.name ?? "")")
print("Email: \(profile.email ?? "")")
print("Email verified: \(profile.emailVerified)")
print("Picture: \(profile.picture ?? "")")
print("Nickname: \(profile.nickname ?? "")")
case .failure(let error):
print("Failed to fetch profile: \(error)")
}
}

Prerequisites: Requires a valid access token from a login flow. The token must have the openid scope to return user claims.

Revoke a refresh token to invalidate it server-side:

Auth0SDK.shared.revokeToken(refreshToken: savedRefreshToken) { result in
switch result {
case .success:
print("Token revoked")
case .failure(let error):
print("Revocation failed: \(error)")
}
}

Note: After revocation, the refresh token can no longer be used to obtain new access tokens. Call this during logout for complete session invalidation.

Auth0SDK.shared.logout { result in
switch result {
case .success:
print("Logged out")
case .failure(let error):
print("Logout failed: \(error)")
}
}
// Federated logout (also logs out of the identity provider)
Auth0SDK.shared.logout(federated: true) { result in
// Handle result...
}

Refresh an expired access token using a refresh token:

Auth0SDK.shared.renewCredentials(refreshToken: savedRefreshToken) { result in
switch result {
case .success(let newCredentials):
print("Token renewed: \(newCredentials.accessToken ?? "")")
case .failure(let error):
print("Renewal failed: \(error)")
}
}

Store and retrieve credentials using the platform’s secure storage (Keychain on iOS, SharedPreferences on Android):

// Save credentials after login
Auth0SDK.shared.login { result in
if case .success(let credentials) = result {
Auth0SDK.shared.saveCredentials(credentials)
}
}
// Check whether stored credentials are available
if Auth0SDK.shared.hasValidCredentials {
print("User has valid stored credentials")
} else {
print("User needs to log in")
}
// Retrieve stored credentials (auto-renews if expired)
Auth0SDK.shared.getCredentials { result in
switch result {
case .success(let credentials):
print("Retrieved credentials: \(credentials.accessToken ?? "")")
case .failure(let error):
print("Failed to retrieve credentials: \(error)")
}
}
// Clear stored credentials
Auth0SDK.shared.clearCredentials()
import SwiftUI
import SkipAuth0
struct LoginView: View {
@State var isLoggedIn = false
@State var userName: String?
@State var errorMessage: String?
var body: some View {
VStack(spacing: 16) {
if isLoggedIn {
if let userName {
Text("Welcome, \(userName)!")
} else {
Text("You are logged in!")
}
Button("Log Out") {
Auth0SDK.shared.logout { result in
if case .success = result {
Auth0SDK.shared.clearCredentials()
isLoggedIn = false
userName = nil
}
}
}
} else {
Text("Please log in")
Button("Log In") {
Auth0SDK.shared.login { result in
switch result {
case .success(let credentials):
Auth0SDK.shared.saveCredentials(credentials)
isLoggedIn = true
// Fetch user profile
if let token = credentials.accessToken {
Auth0SDK.shared.userInfo(accessToken: token) { profileResult in
if case .success(let profile) = profileResult {
userName = profile.name
}
}
}
case .failure(let error):
errorMessage = error.localizedDescription
}
}
}
Button("Sign Up") {
Auth0SDK.shared.signUp(
email: "user@example.com",
password: "securepassword"
) { result in
switch result {
case .success(let credentials):
Auth0SDK.shared.saveCredentials(credentials)
isLoggedIn = true
case .failure(let error):
errorMessage = error.localizedDescription
}
}
}
}
if let errorMessage {
Text(errorMessage).foregroundStyle(.red)
}
}
.padding()
.onAppear {
if Auth0SDK.shared.hasValidCredentials {
Auth0SDK.shared.getCredentials { result in
if case .success = result {
isLoggedIn = true
}
}
}
}
}
}

The main singleton for all Auth0 operations.

Method / PropertyDescription
sharedThe singleton instance
configure(_:)Initialize with an Auth0Config
isConfigured: BoolWhether the SDK has been configured
config: Auth0Config?The current configuration
Web Auth
login(scope:audience:presenting:completion:)Start Universal Login (browser)
logout(federated:presenting:completion:)Clear the session
Authentication API
loginWithCredentials(email:password:scope:audience:completion:)Direct email/password login
signUp(email:password:username:connection:scope:audience:completion:)Register a new user and log in
createUser(email:password:username:connection:completion:)Register a new user without logging in
resetPassword(email:connection:completion:)Send a password reset email
renewCredentials(refreshToken:completion:)Refresh an access token
userInfo(accessToken:completion:)Fetch the user profile
revokeToken(refreshToken:completion:)Revoke a refresh token
Credentials Manager
hasValidCredentials: BoolWhether stored credentials exist
clearCredentials()Remove stored credentials
saveCredentials(_:) -> BoolStore credentials in secure storage
getCredentials(completion:)Retrieve stored credentials (auto-renews)
PropertyDescription
domain: StringAuth0 tenant domain (e.g. "yourapp.us.auth0.com")
clientId: StringOAuth client ID from your Auth0 application
scheme: StringURL scheme for callbacks (e.g. "com.example.myapp")
logoutReturnTo: String?Optional custom return-to URL for logout
PropertyTypeDescription
accessTokenString?OAuth2 access token
idTokenString?OpenID Connect ID token (JWT)
refreshTokenString?Refresh token for renewing credentials
tokenTypeString?Token type (typically "Bearer")
expiresAtDate?When the access token expires
scopeString?Granted OAuth scopes
PropertyTypeDescription
subStringThe user’s unique identifier (the sub claim)
nameString?Full name
givenNameString?Given (first) name
familyNameString?Family (last) name
nicknameString?Nickname
emailString?Email address
emailVerifiedBoolWhether the email has been verified
pictureString?URL of the user’s profile picture
PropertyTypeDescription
emailStringThe user’s email address
usernameString?Username (if the connection requires it)
emailVerifiedBoolWhether the email has been verified
CaseDescription
.notConfiguredconfigure(_:) has not been called
.missingPresenterA platform presenter is required
.webAuthFailed(String)The web authentication flow failed
.authenticationFailed(String)An authentication API call failed
FeatureRequirement
All featuresAuth0 account with a Native application configured
Universal LoginCallback URLs configured in Auth0 dashboard for both platforms
loginWithCredentials”Password” grant type enabled in application settings
signUp / createUser”Password” grant type enabled; sign-ups enabled on the database connection
resetPasswordA database connection (e.g. Username-Password-Authentication)
userInfoAccess token with openid scope
revokeTokenA valid refresh token (request offline_access scope during login)
iOSCFBundleURLSchemes registered in Info.plist
Androidauth0Domain and auth0Scheme manifest placeholders in skip.yml
  • Social / OAuth login — Supported through Universal Login (the browser-based flow handles all identity providers configured in your Auth0 dashboard). Direct social token exchange is not wrapped.
  • MFA (Multi-Factor Authentication) — Handled by Universal Login when configured in your Auth0 dashboard. Programmatic MFA enrollment/verification is not wrapped.
  • Passwordless login (email/SMS codes) — Not yet wrapped. Use the native SDKs directly or Universal Login.
  • Organizations — Auth0 Organizations support is available through Universal Login but not wrapped in the cross-platform API.

This project is a Swift Package Manager module that uses the Skip plugin to build the package for both iOS and Android.

The module can be tested using the standard swift test command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment.

Parity testing can be performed with skip test, which will output a table of the test results for both platforms.