Authentication

iOS Authentication

This page explains OAuth sign-in, JWT login, session handling, and biometric features for the iOS SDK.

Login provider

Choose provider with signIn(provider:):

// Show provider selection sheet (Google + Apple)
let auth = try await sdk.signIn(provider: .all)

// Direct Google login
let auth = try await sdk.signIn(provider: .google)

// Direct Apple login
let auth = try await sdk.signIn(provider: .apple)

One-step login + wallet creation

signInWithCreate combines login and wallet creation into a single call. If the user already has a wallet, it returns immediately without creating a new one.

let auth = try await sdk.signInWithCreate(provider: .all)
// auth.walletAddress is guaranteed to be non-nil on success
  • If needsMigration is true after login, createWallet() is called automatically to trigger the migration flow.
  • If a wallet already exists, no additional creation is performed.

JWT login (token injection)

If your app already has JWT tokens from an external auth system, inject them directly without the OAuth browser flow:

let auth = try await sdk.signInWithJWT(
    accessToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    refreshToken: "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4..."
)
  • refreshToken is optional — if nil, the SDK cannot auto-refresh when the access token expires.
  • Throws CROSSxError.tokenExpired if the provided JWT is already expired.

OAuth callback handling (required)

After ASWebAuthenticationSession login, forward the callback URL into the SDK:

func application(
    _ app: UIApplication,
    open url: URL,
    options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
    return sdk.handleURL(url)
}

If callback handling is missing, signIn() cannot complete.

Session lifecycle

initialize()

Try restoring stored session on app start:

let restored = try await sdk.initialize() // AuthResult? (nil if no session)

isLoggedIn()

Fast local check only (no refresh call):

let loggedIn = sdk.isLoggedIn()

ensureLoggedIn()

Ensures a usable session now (includes restore/refresh attempt):

let ok = await sdk.ensureLoggedIn()

refreshToken()

Explicitly refresh the access token:

let newAccessToken = try await sdk.refreshToken()
  • Returns the current token if still valid, otherwise refreshes via the refresh token.
  • Throws CROSSxError.sessionExpired if the refresh token is also invalid.

Sign-in / Sign-out

// Open OAuth (ASWebAuthenticationSession)
let auth = try await sdk.signIn()

// Sign in and create or migrate wallet in one step
let authWithWallet = try await sdk.signInWithCreate()

// Clear local session
try await sdk.signOut()

After sign-in

After signIn(), continue with wallet APIs (for example, create/resolve address) in the same authenticated session.

let ok = await sdk.ensureLoggedIn()
if !ok { return }

// Create wallet if needed (idempotent on backend side).
let created = try await sdk.createWallet()

// Check wallet existence/state via address list.
let addresses = try await sdk.getAddresses()

Use this pattern when your app flow is "login -> immediately perform wallet action".

Wallet check

Check the wallet status without creating or modifying anything:

let status = try await sdk.checkWallet()

switch status {
case .exists:
    // Wallet exists — proceed with wallet operations
    break
case .migrationRequired:
    // Legacy backup found — call createWallet() to trigger migration
    let wallet = try await sdk.createWallet()
case .notFound:
    // No wallet — call createWallet() to create a new one
    let wallet = try await sdk.createWallet()
}

Password verification

Verify a wallet PIN against the server without caching it:

let isValid = try await sdk.verifyPassword("123456")

Cloud backup status

Both initialize() and signIn() automatically check for a legacy native app wallet backup and include the result in AuthResult.cloudBackupStatus.

let auth = try await sdk.signIn()

if let backup = auth.cloudBackupStatus, backup.hasBackup {
    // A legacy backup exists — the user can recover it via createWallet()
    let recovered = try await sdk.createWallet()
}
  • cloudBackupStatus is nil if the backup check fails (does not affect login success).
  • hasBackup: true means a previous PIN-based backup is available for migration.

Migration status

signIn() also checks the wallet status and sets AuthResult.needsMigration:

let auth = try await sdk.signIn()

if auth.needsMigration == true {
    // Legacy wallet exists — createWallet() will trigger migration UI
    let wallet = try await sdk.createWallet()
}

User info

Retrieve comprehensive user information including provider details and wallet addresses:

let userInfo = try await sdk.getUserInfo()

print(userInfo.data.provider)     // "google" or "apple"
print(userInfo.data.email)        // user email
print(userInfo.data.sub)          // JWT subject (user ID)
print(userInfo.data.providerSub)  // OAuth provider's original sub (Google/Apple)
print(userInfo.data.accessToken)  // current access token
print(userInfo.loginType)         // "google" or "apple"
print(userInfo.addresses)         // [WalletAddressInfo]

Biometric authentication

The SDK supports Face ID / Touch ID to protect the wallet PIN stored in Keychain.

// Check if the device supports biometric auth
let canUse = sdk.canUseBiometric()

// Check if biometric protection is currently enabled
let isEnabled = sdk.isBiometricEnabled()

// Enable or disable biometric protection
try await sdk.setBiometricEnabled(true)
try await sdk.setBiometricEnabled(false)
  • setBiometricEnabled(true) saves the PIN to Keychain with Face ID / Touch ID protection.
  • If the device has no enrolled biometrics, canUseBiometric() returns false.
  • If no PIN is stored, setBiometricEnabled(_:) shows an Enter PIN modal first.

Session expiration handling

When the session expires (both access and refresh tokens are invalid), the SDK throws CROSSxError.sessionExpired. Handle this to redirect the user to login:

do {
    let data = try await sdk.getBalance(address: addr, chainId: chainId)
} catch CROSSxError.sessionExpired {
    // Redirect to login flow
    let auth = try await sdk.signIn()
}

Recommended app flow

  1. App launch: call initialize()
  2. Before protected action: call ensureLoggedIn()
  3. User login action: call signIn() or signInWithCreate()
  4. User logout action: call signOut()

© 2025 NEXUS Co., Ltd. All Rights Reserved.