authentication
dpearson2699/swift-ios-skills
Implement iOS authentication with Sign in with Apple, passkeys, OAuth, and biometric flows.
What is authentication?
Build secure iOS authentication using AuthenticationServices and LocalAuthentication frameworks. Use this skill when implementing Sign in with Apple, passkey/WebAuthn registration, OAuth flows, Password AutoFill, credential state management, and biometric re-authentication.
- Sign in with Apple with ASAuthorizationController and credential handling
- Passkey and WebAuthn registration/sign-in via ASAuthorizationPlatformPublicKeyCredentialProvider
- OAuth and third-party login with ASWebAuthenticationSession
- Credential state checking and revocation handling
- Identity token server-side validation (JWT)
- Biometric re-authentication with LAContext and Password AutoFill
How to install authentication
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill authentication- Enable 'Sign in with Apple' capability in Xcode project settings
- Import AuthenticationServices and LocalAuthentication frameworks
- Set up backend endpoint for identity token validation
How to use authentication
- 1.Add Sign in with Apple capability in Xcode
- 2.Create ASAuthorizationAppleIDProvider and configure request scopes
- 3.Set up ASAuthorizationController with delegate and presentation context provider
- 4.Implement ASAuthorizationControllerDelegate to handle credentials and errors
- 5.Cache email and fullName immediately on first authorization (not available later)
- 6.Send identityToken and authorizationCode to your server for validation
- 7.Check credential state on app launch using getCredentialState(forUserID:)
- 8.Observe ASAuthorizationAppleIDProvider.credentialRevokedNotification for revocation events
Use cases
- Building Sign in with Apple flows with email and name capture on first auth
- Implementing passkey registration and authentication for passwordless login
- Setting up OAuth/third-party web authentication flows
- Checking credential state on app launch to detect revoked access
- Validating Apple identity tokens on your backend server
- iOS app developers building authentication systems
- Teams implementing Sign in with Apple for user onboarding
- Developers adding passwordless (passkey) authentication
- Apps requiring secure credential state management and revocation handling
authentication FAQ
Apple only provides these values on initial sign-up. Cache them immediately to your backend. On subsequent authentications or after app reinstall, these fields return nil.
Check credential state on every app launch with getCredentialState(forUserID:). Also observe credentialRevokedNotification to sign out immediately when the user revokes access in Settings.
No. Always send the identityToken and authorizationCode to your server for validation. Never trust the JWT client-side alone.
identityToken is a JWT containing user claims for immediate use. authorizationCode is a short-lived code your server exchanges for tokens and user info via Apple's token endpoint.
Use LAContext from LocalAuthentication to evaluate biometric policies (Face ID, Touch ID) for sensitive operations after initial Sign in with Apple authentication.
Full instructions (SKILL.md)
Source of truth, from dpearson2699/swift-ios-skills.
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
- 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
) {
let authError = error as? ASAuthorizationError
switch authError?.code {
case .canceled:
break // User dismissed
case .failed:
showError("Authorization failed")
case .invalidResponse:
showError("Invalid response")
case .notHandled:
showError("Not handled")
case .notInteractive:
break // Non-interactive request failed -- expected for silent checks
default:
showError("Unknown error")
}
}
}
Credential Handling
ASAuthorizationAppleIDCredential properties and their behavior:
| Property | Type | First Auth | Subsequent Auth |
|---|---|---|---|
user | String | Always | Always |
email | String? | Provided if requested | nil |
fullName | PersonNameComponents? | Provided if requested | nil |
identityToken | Data? | JWT encoded as UTF-8 data | JWT encoded as UTF-8 data |
authorizationCode | Data? | Short-lived code | Short-lived code |
realUserStatus | ASUserDetectionStatus | Fraud-prevention signal | Do not rely on later attempts |
Critical: email and fullName are provided ONLY on the first
authorization. Cache them immediately during the initial sign-up flow. If the
user later deletes and re-adds the app, these values will not be returned.
func handleCredential(_ credential: ASAuthorizationAppleIDCredential) {
// Always persist the user identifier
let userID = credential.user
// Cache name and email IMMEDIATELY -- only available on first auth
if let fullName = credential.fullName {
let name = PersonNameComponentsFormatter().string(from: fullName)
UserProfile.saveName(name) // Persist to your backend
}
if let email = credential.email {
UserProfile.saveEmail(email) // Persist to your backend
}
}
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.
func sendTokenToServer(credential: ASAuthorizationAppleIDCredential) async throws {
guard let tokenData = credential.identityToken,
let token = String(data: tokenData, encoding: .utf8),
let authCodeData = credential.authorizationCode,
let authCode = String(data: authCodeData, encoding: .utf8) else {
throw AuthError.missingToken
}
var request = URLRequest(url: URL(string: "https://api.example.com/auth/apple")!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONEncoder().encode(
["identityToken": token, "authorizationCode": authCode]
)
let (data, response) = try await URLSession.shared.data(for: request)
guard (response as? HTTPURLResponse)?.statusCode == 200 else {
throw AuthError.serverValidationFailed
}
let session = try JSONDecoder().decode(SessionResponse.self, from: data)
// Store session token in Keychain -- see references/keychain-biometric.md
try KeychainHelper.save(session.accessToken, forKey: "accessToken")
}
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, exp not passed.
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 for passwordless WebAuthn-style registration and sign-in. The
app must have an Associated Domains entitlement for the relying party domain
using the webcredentials: service; passkey requests fail for services the app
has not configured as associated domains.
For platform passkeys synced through iCloud Keychain, request a server-provided
challenge and create requests with ASAuthorizationPlatformPublicKeyCredentialProvider:
let challenge: Data = try await server.registrationChallenge()
let userID: Data = try await server.passkeyUserID()
let provider = ASAuthorizationPlatformPublicKeyCredentialProvider(
relyingPartyIdentifier: "example.com"
)
let request = provider.createCredentialRegistrationRequest(
challenge: challenge,
name: username,
userID: userID
)
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()
For sign-in, use createCredentialAssertionRequest(challenge:) with a fresh
server challenge, then send the resulting registration or assertion object to
the relying-party server for verification:
let request = provider.createCredentialAssertionRequest(challenge: challenge)
switch authorization.credential {
case let registration as ASAuthorizationPlatformPublicKeyCredentialRegistration:
try await server.finishPasskeyRegistration(registration)
case let assertion as ASAuthorizationPlatformPublicKeyCredentialAssertion:
try await server.finishPasskeySignIn(assertion)
default:
break
}
For inline passkey suggestions, set the username field's textContentType to
.username, include the passkey assertion request in the controller, and call
performAutoFillAssistedRequests(). Use ASAuthorizationSecurityKeyPublicKeyCredentialProvider
only when the user must authenticate with a physical security key. See
references/passkeys.md for complete registration,
assertion, AutoFill, and security-key patterns.
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
Use ASAuthorizationPasswordProvider to offer saved keychain credentials
alongside Sign in with Apple:
func performSignIn() {
let appleIDRequest = ASAuthorizationAppleIDProvider().createRequest()
appleIDRequest.requestedScopes = [.fullName, .email]
let passwordRequest = ASAuthorizationPasswordProvider().createRequest()
let controller = ASAuthorizationController(
authorizationRequests: [appleIDRequest, passwordRequest]
)
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()
}
// In delegate:
func authorizationController(
controller: ASAuthorizationController,
didCompleteWithAuthorization authorization: ASAuthorization
) {
switch authorization.credential {
case let appleIDCredential as ASAuthorizationAppleIDCredential:
handleAppleIDLogin(appleIDCredential)
case let passwordCredential as ASPasswordCredential:
// User selected a saved password from keychain
signInWithPassword(
username: passwordCredential.user,
password: passwordCredential.password
)
default:
break
}
}
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 SecAccessControl and .biometryCurrentSet patterns.
import LocalAuthentication
func authenticateWithBiometrics() async throws -> Bool {
let context = LAContext()
var error: NSError?
guard context.canEvaluatePolicy(
.deviceOwnerAuthenticationWithBiometrics, error: &error
) else {
throw AuthError.biometricsUnavailable
}
return try await context.evaluatePolicy(
.deviceOwnerAuthenticationWithBiometrics,
localizedReason: "Sign in to your account"
)
}
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, handle .success and .failure, downcast to
ASAuthorizationAppleIDCredential, and send the credential through the same
server-validation flow as UIKit. 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
.notInteractiveas the normal "no local credential" path. - Force-unwrapping
emailorfullName. Cache them on first authorization and handlenillater. - Creating an
ASAuthorizationControllerwithout 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
-
ASAuthorizationControllerPresentationContextProvidingimplemented - Credential state checked on every app launch (
getCredentialState(forUserID:completion:)) -
credentialRevokedNotificationobserver registered; sign-out handled -
emailandfullNamecached on first authorization (not assumed available later) -
identityTokensent to server for validation, not trusted client-side only - Tokens stored in Keychain, not UserDefaults or files
-
performExistingAccountSetupFlowscalled before showing login UI - Error cases handled:
.canceled,.failed,.notInteractive -
NSFaceIDUsageDescriptionin Info.plist for biometric auth -
ASWebAuthenticationSessionused for OAuth (notWKWebView) -
prefersEphemeralWebBrowserSessionset for OAuth when appropriate -
textContentTypeset 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
- Passkey patterns: references/passkeys.md
- AuthenticationServices
- ASAuthorizationAppleIDProvider
- ASAuthorizationAppleIDCredential
- ASAuthorizationController
- ASWebAuthenticationSession
- Supporting passkeys
- ASAuthorizationPlatformPublicKeyCredentialProvider
- ASAuthorizationPasswordProvider
- SignInWithAppleButton
- Implementing User Authentication with Sign in with Apple
Related skills
More from dpearson2699/swift-ios-skills and the wider catalog.

avkit
Create system-standard video players with AVKit for iOS—supports Picture-in-Picture, AirPlay, subtitles, and SwiftUI integration.

background-processing
Schedule and execute background work on iOS using BGTaskScheduler, background URLSession, and push notifications.

browserenginekit
Build alternative browser engines for iOS/iPadOS with process isolation, XPC communication, and system integration.

callkit
Implement native iOS VoIP calling with CallKit and PushKit integration.

carplay
Build CarPlay-enabled apps for vehicle displays using Apple's template-based UI framework.

cloudkit
Implement CloudKit and iCloud sync for iOS/macOS apps with record CRUD, queries, subscriptions, and SwiftData integration.