authentication

$npx mdskill add dpearson2699/swift-ios-skills/authentication

Implement iOS authentication flows using AuthenticationServices and LocalAuthentication.

  • Enables Sign in with Apple, passkeys, OAuth, and biometric re-authentication.
  • Depends on AuthenticationServices, LocalAuthentication, and ASWebAuthenticationSession.
  • Selects flow based on capability, credential state, and user intent.
  • Returns authorization tokens, credential state updates, or biometric success/failure.

SKILL.md

.github/skills/authenticationView on GitHub ↗
---
name: authentication
description: "Implement iOS authentication flows with AuthenticationServices and LocalAuthentication. Use when building Sign in with Apple, passkey/WebAuthn registration or sign-in with ASAuthorizationPlatformPublicKeyCredentialProvider, ASAuthorizationController credential state and revocation handling, ASWebAuthenticationSession OAuth or third-party login, Password AutoFill, identity-token server validation, or local biometric re-authentication with LAContext."
---

# Authentication

Implement authentication flows on iOS using the AuthenticationServices
framework, including Sign in with Apple, passkeys, OAuth/third-party web
auth, Password AutoFill, and biometric re-authentication.

## Contents

- [Sign in with Apple](#sign-in-with-apple)
- [Credential Handling](#credential-handling)
- [Credential State Checking](#credential-state-checking)
- [Token Validation](#token-validation)
- [Existing Account Setup Flows](#existing-account-setup-flows)
- [Passkeys](#passkeys)
- [ASWebAuthenticationSession (OAuth)](#aswebauthenticationsession-oauth)
- [Password AutoFill Credentials](#password-autofill-credentials)
- [Biometric Authentication](#biometric-authentication)
- [Security Boundaries](#security-boundaries)
- [SwiftUI SignInWithAppleButton](#swiftui-signinwithapplebutton)
- [Common Mistakes](#common-mistakes)
- [Review Checklist](#review-checklist)
- [References](#references)

## Sign in with Apple

Add the "Sign in with Apple" capability in Xcode before using these APIs.

### UIKit: ASAuthorizationController Setup

```swift
import AuthenticationServices

final class LoginViewController: UIViewController {
    func startSignInWithApple() {
        let provider = ASAuthorizationAppleIDProvider()
        let request = provider.createRequest()
        request.requestedScopes = [.fullName, .email]

        let controller = ASAuthorizationController(authorizationRequests: [request])
        controller.delegate = self
        controller.presentationContextProvider = self
        controller.performRequests()
    }
}

extension LoginViewController: ASAuthorizationControllerPresentationContextProviding {
    func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
        view.window!
    }
}
```

### Delegate: Handling Success and Failure

```swift
extension LoginViewController: ASAuthorizationControllerDelegate {
    func authorizationController(
        controller: ASAuthorizationController,
        didCompleteWithAuthorization authorization: ASAuthorization
    ) {
        guard let credential = authorization.credential
            as? ASAuthorizationAppleIDCredential else { return }

        let userID = credential.user  // Stable, unique, per-team identifier
        let email = credential.email  // nil after first authorization
        let fullName = credential.fullName  // nil after first authorization
        let identityToken = credential.identityToken  // JWT for server validation
        let authCode = credential.authorizationCode  // Short-lived code for server exchange

        // Save userID to Keychain for credential state checks
        // See references/keychain-biometric.md for Keychain patterns
        saveUserID(userID)

        // Send identityToken and authCode to your server
        authenticateWithServer(identityToken: identityToken, authCode: authCode)
    }

    func authorizationController(
        controller: ASAuthorizationController,
        didCompleteWithError error: any Error
    ) {
        switch (error as? ASAuthorizationError)?.code {
        case .canceled, .notInteractive:
            break
        case .failed:
            showError("Authorization failed")
        default:
            showError("Authorization failed: \(error.localizedDescription)")
        }
    }
}
```

## Credential Handling

| Credential data | Required handling |
|---|---|
| `user` | Persist this stable, per-team identifier for credential-state checks. |
| `email`, `fullName` | These optional values arrive only on first authorization; cache them immediately. |
| `identityToken`, `authorizationCode` | Send them to the server for validation or exchange; never trust them as client-side proof. |

Treat `realUserStatus` only as a fraud-prevention signal, not authentication
proof.

## Credential State Checking

Check credential state on every app launch. The user may revoke access at
any time via Settings > Apple Account > Sign-In & Security.

```swift
func checkCredentialState() {
    let provider = ASAuthorizationAppleIDProvider()
    guard let userID = loadSavedUserID() else {
        showLoginScreen()
        return
    }

    provider.getCredentialState(forUserID: userID) { state, _ in
        DispatchQueue.main.async {
            switch state {
            case .authorized:
                proceedToMainApp()
            case .revoked:
                // User revoked -- sign out and clear local data
                signOut()
                showLoginScreen()
            case .notFound:
                showLoginScreen()
            case .transferred:
                // App transferred to new team -- migrate user identifier
                migrateUser()
            @unknown default:
                showLoginScreen()
            }
        }
    }
}
```

### Credential Revocation Notification

```swift
NotificationCenter.default.addObserver(
    forName: ASAuthorizationAppleIDProvider.credentialRevokedNotification,
    object: nil,
    queue: .main
) { _ in
    // Sign out immediately
    AuthManager.shared.signOut()
}
```

## Token Validation

The `identityToken` is a JWT. Send it to your server for validation --
never trust it client-side alone.

Server-side, validate the JWT against Apple's public keys at
`https://appleid.apple.com/auth/keys` (JWKS). Verify: `iss` is
`https://appleid.apple.com`, `aud` matches your bundle ID, and `exp` has not
passed. Exchange the short-lived authorization code on the server and store
the resulting app session token in Keychain.

## Existing Account Setup Flows

On launch, silently check for existing Sign in with Apple and password
credentials before showing a login screen:

```swift
func performExistingAccountSetupFlows() {
    let appleIDRequest = ASAuthorizationAppleIDProvider().createRequest()
    let passwordRequest = ASAuthorizationPasswordProvider().createRequest()

    let controller = ASAuthorizationController(
        authorizationRequests: [appleIDRequest, passwordRequest]
    )
    controller.delegate = self
    controller.presentationContextProvider = self
    controller.performRequests(
        options: .preferImmediatelyAvailableCredentials
    )
}
```

Call this in `viewDidAppear` or on app launch. If no existing credentials
are found, the delegate receives a `.notInteractive` error -- handle it
silently and show your normal login UI.

## Passkeys

Use passkeys only for a relying-party domain configured with a `webcredentials:`
Associated Domain and AASA entry. Registration and assertion each require a
fresh server challenge, an `ASAuthorizationPlatformPublicKeyCredentialProvider`,
an authorization controller with an active presentation anchor, and server-side
verification before issuing a session.

Load [references/passkeys.md](references/passkeys.md) for the canonical
registration, assertion, result handling, AutoFill-assisted, and physical
security-key flows.

## ASWebAuthenticationSession (OAuth)

Use `ASWebAuthenticationSession` for OAuth and third-party authentication
(Google, GitHub, etc.). Never use `WKWebView` for auth flows.

```swift
import AuthenticationServices

final class OAuthController: NSObject, ASWebAuthenticationPresentationContextProviding {
    private weak var presentationAnchor: ASPresentationAnchor?

    init(presentationAnchor: ASPresentationAnchor) {
        self.presentationAnchor = presentationAnchor
    }

    func startOAuthFlow() {
        let authURL = URL(string:
            "https://provider.com/oauth/authorize?client_id=YOUR_ID&redirect_uri=myapp://callback&response_type=code"
        )!
        let session = ASWebAuthenticationSession(
            url: authURL, callback: .customScheme("myapp")
        ) { callbackURL, error in
            guard let callbackURL, error == nil,
                  let code = URLComponents(url: callbackURL, resolvingAgainstBaseURL: false)?
                      .queryItems?.first(where: { $0.name == "code" })?.value else { return }
            Task { await self.exchangeCodeForTokens(code) }
        }
        session.presentationContextProvider = self
        session.prefersEphemeralWebBrowserSession = true  // No shared cookies
        session.start()
    }

    func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
        guard let presentationAnchor else {
            fatalError("ASWebAuthenticationSession needs the active window")
        }
        return presentationAnchor
    }
}
```

In SwiftUI, use `@Environment(\.webAuthenticationSession)` and call
`authenticate(using:callback:preferredBrowserSession:additionalHeaderFields:)`
with `.customScheme("myapp")` or `.https(host:path:)`; prefer `.ephemeral`
only when the provider flow should avoid shared browser cookies.

## Password AutoFill Credentials

Offer `ASAuthorizationPasswordProvider` alongside Sign in with Apple using the
single controller in [Existing Account Setup Flows](#existing-account-setup-flows).
Handle `ASPasswordCredential` in that controller's delegate.

Set `textContentType` on text fields for AutoFill to work:

```swift
usernameField.textContentType = .username
passwordField.textContentType = .password
```

## Biometric Authentication

Use `LAContext` from LocalAuthentication for local re-authentication before
showing account settings or starting sensitive actions. Do not treat a returned
`Bool` as proof to unlock a stored secret; protect secrets with Keychain access
control instead. See [references/keychain-biometric.md](references/keychain-biometric.md)
for the canonical `LAContext`, fallback, `SecAccessControl`, and
`.biometryCurrentSet` patterns.

**Required:** Add `NSFaceIDUsageDescription` to Info.plist. Missing this
key crashes on Face ID devices.

## Security Boundaries

This skill owns user-facing account authentication: Sign in with Apple,
passkeys, Password AutoFill, ASAuthorizationController, OAuth session
presentation, credential state, and local biometric re-authentication. Route
deep security work to `swift-security`: Keychain architecture/migration,
CryptoKit, Secure Enclave, certificate pinning/trust, keychain sharing, storage
hardening, and OWASP MASVS/MASTG. Keep only the storage minimum here: tokens and
secrets belong in Keychain; `LAContext.evaluatePolicy` alone must not release
protected secrets.

## SwiftUI SignInWithAppleButton

Use `SignInWithAppleButton` in SwiftUI views when the login surface is SwiftUI.
Request `.fullName` and `.email`, downcast a successful result to
`ASAuthorizationAppleIDCredential`, and pass it to the shared
[Token Validation](#token-validation) flow. Style with
`.signInWithAppleButtonStyle(...)`.

## Common Mistakes

- Assuming a saved local session means the Apple ID credential is still valid.
  Check credential state at launch and handle revocation notifications.
- Showing a full login screen before trying existing account setup flows.
  Treat `.notInteractive` as the normal "no local credential" path.
- Force-unwrapping `email` or `fullName`. Cache them on first authorization and
  handle `nil` later.
- Creating an `ASAuthorizationController` without a presentation context
  provider. Authorization UI needs the active presentation anchor.
- Storing identity tokens, authorization codes, access tokens, passwords, or
  passkey server state in `UserDefaults`, files, or Core Data. Store secrets in
  Keychain and keep relying-party passkey verification server-side.
- Adding passkey requests without `webcredentials:` Associated Domains for the
  relying-party domain, or trying to use app-native passkeys for unrelated
  websites.
- Expanding authentication work into CryptoKit, Secure Enclave, certificate
  pinning, or OWASP MASVS. Route those to `swift-security`.

## Review Checklist

- [ ] "Sign in with Apple" capability added in Xcode project
- [ ] `ASAuthorizationControllerPresentationContextProviding` implemented
- [ ] Credential state checked on every app launch (`getCredentialState(forUserID:completion:)`)
- [ ] `credentialRevokedNotification` observer registered; sign-out handled
- [ ] `email` and `fullName` cached on first authorization (not assumed available later)
- [ ] `identityToken` sent to server for validation, not trusted client-side only
- [ ] Tokens stored in Keychain, not UserDefaults or files
- [ ] `performExistingAccountSetupFlows` called before showing login UI
- [ ] Error cases handled: `.canceled`, `.failed`, `.notInteractive`
- [ ] `NSFaceIDUsageDescription` in Info.plist for biometric auth
- [ ] `ASWebAuthenticationSession` used for OAuth (not `WKWebView`)
- [ ] `prefersEphemeralWebBrowserSession` set for OAuth when appropriate
- [ ] `textContentType` set on username/password fields for AutoFill
- [ ] Passkey relying party has `webcredentials:` Associated Domains configured
- [ ] Passkey registration/assertion challenges come from the server and are verified server-side
- [ ] Deep Keychain, CryptoKit, Secure Enclave, certificate pinning, and MASVS work routed to `swift-security`

## References

- Keychain & biometric patterns: [references/keychain-biometric.md](references/keychain-biometric.md)
- Passkey patterns: [references/passkeys.md](references/passkeys.md)
- [AuthenticationServices](https://sosumi.ai/documentation/authenticationservices)
- [ASAuthorizationAppleIDProvider](https://sosumi.ai/documentation/authenticationservices/asauthorizationappleidprovider)
- [ASAuthorizationAppleIDCredential](https://sosumi.ai/documentation/authenticationservices/asauthorizationappleidcredential)
- [ASAuthorizationController](https://sosumi.ai/documentation/authenticationservices/asauthorizationcontroller)
- [ASWebAuthenticationSession](https://sosumi.ai/documentation/authenticationservices/aswebauthenticationsession)
- [Supporting passkeys](https://sosumi.ai/documentation/authenticationservices/supporting-passkeys)
- [ASAuthorizationPlatformPublicKeyCredentialProvider](https://sosumi.ai/documentation/authenticationservices/asauthorizationplatformpublickeycredentialprovider)
- [ASAuthorizationPasswordProvider](https://sosumi.ai/documentation/authenticationservices/asauthorizationpasswordprovider)
- [SignInWithAppleButton](https://sosumi.ai/documentation/authenticationservices/signinwithapplebutton)
- [Implementing User Authentication with Sign in with Apple](https://sosumi.ai/documentation/authenticationservices/implementing-user-authentication-with-sign-in-with-apple)

More from dpearson2699/swift-ios-skills

SkillDescription
accessorysetupkitDiscover and configure Bluetooth and Wi-Fi accessories using AccessorySetupKit. Use when presenting a privacy-preserving accessory picker, defining discovery descriptors for BLE or Wi-Fi devices, handling accessory session events, migrating from CoreBluetooth permission-based scanning, or setting up accessories without requiring broad Bluetooth permissions.
activitykitImplement, review, or improve Live Activities and Dynamic Island experiences in iOS apps using ActivityKit. Use when building real-time updating widgets for the Lock Screen and Dynamic Island — delivery tracking, sports scores, ride-sharing status, workout timers, media playback, or any time-sensitive information that updates in real time. Also use when working with ActivityKit, ActivityAttributes, Activity lifecycle (request/update/end), Dynamic Island layouts (compact/minimal/expanded), push-to-update Live Activities, or Lock Screen live widgets.
adattributionkitMeasure ad effectiveness with privacy-preserving attribution using AdAttributionKit. Use when registering ad impressions, handling attribution postbacks, updating conversion values, implementing re-engagement attribution, configuring publisher or advertiser apps, or replacing SKAdNetwork with AdAttributionKit for ad measurement.
alarmkitImplement AlarmKit alarms and countdown timers for iOS and iPadOS with Lock Screen, Dynamic Island, StandBy, and paired Apple Watch system UI. Covers AlarmManager scheduling, AlarmAttributes and AlarmPresentation, system Stop and AlarmButton secondary actions, authorization, state observation, countdown widget-extension handoff, and Live Activity integration. Use when building wake-up alarms, countdown timers, or alarm-style alerts that need Apple's system alarm experience.
app-clipsBuild iOS App Clips with invocation URLs, App Clip Codes, NFC, QR codes, Safari banners, Maps, Messages, target setup, App Store Connect experiences, size/capability constraints, NSUserActivity routing, SKOverlay promotion, App Group/keychain handoff, ephemeral notifications, location confirmation, and full-app migration. Use when creating App Clips or wiring App Clip invocation, experience configuration, or full-app handoff.
app-intentsImplement App Intents for Siri, Shortcuts, Spotlight, widgets, Control Center, and Apple Intelligence on iOS. Covers AppIntent actions, AppEntity and EntityQuery models, AppShortcutsProvider phrases, IndexedEntity Spotlight indexing, WidgetConfigurationIntent, SnippetIntent, and assistant schemas. Use when exposing app actions or entities to system surfaces.
app-store-reviewAudits App Store submission readiness and rejection risk across current review guidelines, PrivacyInfo.xcprivacy and required-reason APIs, privacy labels, ATT, StoreKit payments, metadata, entitlements, widgets, and Live Activities. Use when preparing a submission, responding to rejection, reconciling privacy evidence, or separating upload blockers from cleanup.
apple-on-device-aiBuild private, on-device AI features on iPhone, iPad, and Mac with Foundation Models, Core ML, MLX Swift, or llama.cpp. Use when choosing an Apple-local model runtime, building an Apple Intelligence chatbot or tool-calling feature, running an LLM on Apple Silicon, converting or compressing a Python model for Core ML, or comparing on-device inference backends. For Swift Core ML loading and prediction code, use the coreml skill.
appmigrationkitTransfer app data to or from other platforms using AppMigrationKit. Use when implementing system-orchestrated one-time migration between iOS and Android or another platform, building an AppMigrationExtension, packaging transportable resources with ResourcesArchiver, importing resources on the destination device, reporting import progress, handling migration errors and app group cleanup, checking MigrationStatus, or testing migration code with AppMigrationTester.
audioaccessorykitSupport automatic audio switching for paired third-party Bluetooth headphones or earbuds with AudioAccessoryKit. Use when a companion app registers an audio accessory, an app extension reports worn/removed placement or connected source-device changes, or AccessoryControlDevice capabilities and errors need handling. Do not use for general AVAudioSession routing, Bluetooth transport, or initial accessory pairing.