Skip to content

sdk/ios

Swift SDK for iOS and macOS using ASWebAuthenticationSession, PKCE S256 authorization code flow, and Keychain token storage.

Status

Package status is Implemented · verified locally. Unit tests (19 passed) run on macOS targeting an iOS simulator. Real IdP round-trip on a running XID instance is pending manual verification. This page documents implemented behavior; it is not a production-readiness claim.

Requirements

  • iOS 16+ / macOS 13+
  • Swift 5.9+ and Xcode 15+
  • No third-party dependencies — uses Apple system frameworks only

Installation

Add the package via Swift Package Manager in Xcode (File -> Add Package Dependencies) or directly in Package.swift:

// Package.swift
dependencies: [
    .package(url: "https://github.com/StringKe/xid", from: "0.1.0"),
],
targets: [
    .target(name: "YourApp", dependencies: [.product(name: "Xid", package: "xid")]),
]

Quick start

import Xid

// 1. Configure in @main App.init
Xid.shared.configure(options: XidConfiguration(
    issuer: URL(string: "https://xid.dev")!,
    clientId: "your_client_id",
    redirectUri: URL(string: "com.example.app://auth/callback")!,
    scopes: ["openid", "profile", "email", "offline_access"]
))

// 2. Sign in (opens ASWebAuthenticationSession)
try await Xid.shared.signIn()

// 3. Handle redirect in SceneDelegate
let session = try await Xid.shared.handleRedirect(url: callbackUrl)

// 4. Get current session (auto-refreshes near expiry)
if let session = try await Xid.shared.getSession() {
    let token = try await Xid.shared.getAccessToken()
}

// 5. Sign out
try await Xid.shared.signOut(callEndSession: true)

Core API

Method Description
configure(options:) Initialize with issuer, clientId, redirectUri, scopes. Call before all others.
signIn(options:) async throws Open ASWebAuthenticationSession with PKCE S256 authorization URL. Returns when the browser session ends.
handleRedirect(url:) async throws -> XidSession Validate OAuth state, exchange authorization code at the token endpoint, persist tokens to Keychain, and return a session.
getSession() async throws -> XidSession? Return the stored session, triggering a refresh token rotation if near expiry.
getAccessToken(forceRefresh:) async throws -> String Return a valid access token string, refreshing automatically if needed.
signOut(callEndSession:) async throws Clear Keychain tokens. Pass true to call the end_session endpoint via the browser.
setTokenStorage(_:) throws Replace the default KeychainTokenStorage with a custom TokenStorageAdapter implementation.

Storage adapter

The default storage uses Keychain with kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly — tokens are not synced to iCloud Keychain. Implement the TokenStorageAdapter protocol to use an enterprise Keychain policy:

struct EnterpriseKeychain: TokenStorageAdapter {
    func save(key: String, value: String) throws { /* ... */ }
    func load(key: String) throws -> String? { /* ... */ }
    func delete(key: String) throws { /* ... */ }
}
try Xid.shared.setTokenStorage(EnterpriseKeychain())

Security

  • Public client — no client secret stored or transmitted.
  • PKCE S256 only. Server rejects plain challenge method.
  • Random OAuth state generated per request; validated on redirect to prevent CSRF.
  • PKCE code_verifier written to Keychain only for the duration of the authorization flow and deleted immediately after the code exchange.
  • ASWebAuthenticationSession launched with prefersEphemeralWebBrowserSession = true to avoid sharing browser cookies across apps.

Known limitations

  • JWKS-backed ES256/RS256 ID token verification, end_session logout, and refresh single-flight are implemented and locally tested. A real IdP round-trip on an iOS device or simulator is still required before L4 support.
  • Keychain behavior must be verified in an Xcode device or simulator test run.
Navigation

Type to search...

Use arrow keys to navigateEnter to selectEscape to close