mcpbeat

Authentication

dpearson2699/authentication

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.

9k tokens
context cost
the whole folder, loaded on every use
4
files
instructions only
0
copies elsewhere
how many repositories repackaged it
960
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill authentication

The instruction itself

19 sections, as written by the author

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
  • Credential Handling
  • Credential State Checking
  • Token Validation
  • Existing Account Setup Flows
  • Passkeys
  • ASWebAuthenticationSession (OAuth)
  • Password AutoFill Credentials
  • Biometric Authentication
  • Security Boundaries
  • SwiftUI SignInWithAppleButton
  • Common Mistakes
  • Review Checklist
  • References

Sign in with Apple

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

UIKit: ASAuthorizationController Setup

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

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.

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

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:

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 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.

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.

Handle ASPasswordCredential in that controller's delegate.

Set textContentType on text fields for AutoFill to work:

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

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 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

How to use it

Copy the folder

Take dpearson2699/authentication from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.