{"id":"15d9ee11-2f18-4898-84da-301a8d181042","shortId":"5NFt8M","kind":"skill","title":"Authentication","tagline":"Swift Ios Skills skill by Dpearson2699","description":"# Authentication\n\nImplement authentication flows on iOS using the AuthenticationServices\nframework, including Sign in with Apple, OAuth/third-party web auth,\nPassword AutoFill, and biometric authentication.\n\n## Contents\n\n- [Sign in with Apple](#sign-in-with-apple)\n- [Credential Handling](#credential-handling)\n- [Credential State Checking](#credential-state-checking)\n- [Token Validation](#token-validation)\n- [Existing Account Setup Flows](#existing-account-setup-flows)\n- [ASWebAuthenticationSession (OAuth)](#aswebauthenticationsession-oauth)\n- [Password AutoFill Credentials](#password-autofill-credentials)\n- [Biometric Authentication](#biometric-authentication)\n- [SwiftUI SignInWithAppleButton](#swiftui-signinwithapplebutton)\n- [Common Mistakes](#common-mistakes)\n- [Review Checklist](#review-checklist)\n- [References](#references)\n\n## Sign in with Apple\n\nAdd the \"Sign in with Apple\" capability in Xcode before using these APIs.\n\n### UIKit: ASAuthorizationController Setup\n\n```swift\nimport AuthenticationServices\n\nfinal class LoginViewController: UIViewController {\n    func startSignInWithApple() {\n        let provider = ASAuthorizationAppleIDProvider()\n        let request = provider.createRequest()\n        request.requestedScopes = [.fullName, .email]\n\n        let controller = ASAuthorizationController(authorizationRequests: [request])\n        controller.delegate = self\n        controller.presentationContextProvider = self\n        controller.performRequests()\n    }\n}\n\nextension LoginViewController: ASAuthorizationControllerPresentationContextProviding {\n    func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {\n        view.window!\n    }\n}\n```\n\n### Delegate: Handling Success and Failure\n\n```swift\nextension LoginViewController: ASAuthorizationControllerDelegate {\n    func authorizationController(\n        controller: ASAuthorizationController,\n        didCompleteWithAuthorization authorization: ASAuthorization\n    ) {\n        guard let credential = authorization.credential\n            as? ASAuthorizationAppleIDCredential else { return }\n\n        let userID = credential.user  // Stable, unique, per-team identifier\n        let email = credential.email  // nil after first authorization\n        let fullName = credential.fullName  // nil after first authorization\n        let identityToken = credential.identityToken  // JWT for server validation\n        let authCode = credential.authorizationCode  // Short-lived code for server exchange\n\n        // Save userID to Keychain for credential state checks\n        // See references/keychain-biometric.md for Keychain patterns\n        saveUserID(userID)\n\n        // Send identityToken and authCode to your server\n        authenticateWithServer(identityToken: identityToken, authCode: authCode)\n    }\n\n    func authorizationController(\n        controller: ASAuthorizationController,\n        didCompleteWithError error: any Error\n    ) {\n        let authError = error as? ASAuthorizationError\n        switch authError?.code {\n        case .canceled:\n            break  // User dismissed\n        case .failed:\n            showError(\"Authorization failed\")\n        case .invalidResponse:\n            showError(\"Invalid response\")\n        case .notHandled:\n            showError(\"Not handled\")\n        case .notInteractive:\n            break  // Non-interactive request failed -- expected for silent checks\n        default:\n            showError(\"Unknown error\")\n        }\n    }\n}\n```\n\n## Credential Handling\n\n`ASAuthorizationAppleIDCredential` properties and their behavior:\n\n| Property | Type | First Auth | Subsequent Auth |\n|---|---|---|---|\n| `user` | `String` | Always | Always |\n| `email` | `String?` | Provided if requested | `nil` |\n| `fullName` | `PersonNameComponents?` | Provided if requested | `nil` |\n| `identityToken` | `Data?` | JWT (Base64) | JWT (Base64) |\n| `authorizationCode` | `Data?` | Short-lived code | Short-lived code |\n| `realUserStatus` | `ASUserDetectionStatus` | `.likelyReal` / `.unknown` | `.unknown` |\n\n**Critical:** `email` and `fullName` are provided ONLY on the first\nauthorization. Cache them immediately during the initial sign-up flow. If the\nuser later deletes and re-adds the app, these values will not be returned.\n\n```swift\nfunc handleCredential(_ credential: ASAuthorizationAppleIDCredential) {\n    // Always persist the user identifier\n    let userID = credential.user\n\n    // Cache name and email IMMEDIATELY -- only available on first auth\n    if let fullName = credential.fullName {\n        let name = PersonNameComponentsFormatter().string(from: fullName)\n        UserProfile.saveName(name)  // Persist to your backend\n    }\n    if let email = credential.email {\n        UserProfile.saveEmail(email)  // Persist to your backend\n    }\n}\n```\n\n## Credential State Checking\n\nCheck credential state on every app launch. The user may revoke access at\nany time via Settings > Apple Account > Sign-In & Security.\n\n```swift\nfunc checkCredentialState() async {\n    let provider = ASAuthorizationAppleIDProvider()\n    guard let userID = loadSavedUserID() else {\n        showLoginScreen()\n        return\n    }\n\n    do {\n        let state = try await provider.credentialState(forUserID: userID)\n        switch state {\n        case .authorized:\n            proceedToMainApp()\n        case .revoked:\n            // User revoked -- sign out and clear local data\n            signOut()\n            showLoginScreen()\n        case .notFound:\n            showLoginScreen()\n        case .transferred:\n            // App transferred to new team -- migrate user identifier\n            migrateUser()\n        @unknown default:\n            showLoginScreen()\n        }\n    } catch {\n        // Network error -- allow offline access or retry\n        proceedToMainApp()\n    }\n}\n```\n\n### Credential Revocation Notification\n\n```swift\nNotificationCenter.default.addObserver(\n    forName: ASAuthorizationAppleIDProvider.credentialRevokedNotification,\n    object: nil,\n    queue: .main\n) { _ in\n    // Sign out immediately\n    AuthManager.shared.signOut()\n}\n```\n\n## Token Validation\n\nThe `identityToken` is a JWT. Send it to your server for validation --\nnever trust it client-side alone.\n\n```swift\nfunc sendTokenToServer(credential: ASAuthorizationAppleIDCredential) async throws {\n    guard let tokenData = credential.identityToken,\n          let token = String(data: tokenData, encoding: .utf8),\n          let authCodeData = credential.authorizationCode,\n          let authCode = String(data: authCodeData, encoding: .utf8) else {\n        throw AuthError.missingToken\n    }\n\n    var request = URLRequest(url: URL(string: \"https://api.example.com/auth/apple\")!)\n    request.httpMethod = \"POST\"\n    request.setValue(\"application/json\", forHTTPHeaderField: \"Content-Type\")\n    request.httpBody = try JSONEncoder().encode(\n        [\"identityToken\": token, \"authorizationCode\": authCode]\n    )\n\n    let (data, response) = try await URLSession.shared.data(for: request)\n    guard (response as? HTTPURLResponse)?.statusCode == 200 else {\n        throw AuthError.serverValidationFailed\n    }\n    let session = try JSONDecoder().decode(SessionResponse.self, from: data)\n    // Store session token in Keychain -- see references/keychain-biometric.md\n    try KeychainHelper.save(session.accessToken, forKey: \"accessToken\")\n}\n```\n\nServer-side, validate the JWT against Apple's public keys at\n`https://appleid.apple.com/auth/keys` (JWKS). Verify: `iss` is\n`https://appleid.apple.com`, `aud` matches your bundle ID, `exp` not passed.\n\n## Existing Account Setup Flows\n\nOn launch, silently check for existing Sign in with Apple and password\ncredentials before showing a login screen:\n\n```swift\nfunc performExistingAccountSetupFlows() {\n    let appleIDRequest = ASAuthorizationAppleIDProvider().createRequest()\n    let passwordRequest = ASAuthorizationPasswordProvider().createRequest()\n\n    let controller = ASAuthorizationController(\n        authorizationRequests: [appleIDRequest, passwordRequest]\n    )\n    controller.delegate = self\n    controller.presentationContextProvider = self\n    controller.performRequests(\n        options: .preferImmediatelyAvailableCredentials\n    )\n}\n```\n\nCall this in `viewDidAppear` or on app launch. If no existing credentials\nare found, the delegate receives a `.notInteractive` error -- handle it\nsilently and show your normal login UI.\n\n## ASWebAuthenticationSession (OAuth)\n\nUse `ASWebAuthenticationSession` for OAuth and third-party authentication\n(Google, GitHub, etc.). Never use `WKWebView` for auth flows.\n\n```swift\nimport AuthenticationServices\n\nfinal class OAuthController: NSObject, ASWebAuthenticationPresentationContextProviding {\n    func startOAuthFlow() {\n        let authURL = URL(string:\n            \"https://provider.com/oauth/authorize?client_id=YOUR_ID&redirect_uri=myapp://callback&response_type=code\"\n        )!\n        let session = ASWebAuthenticationSession(\n            url: authURL, callback: .customScheme(\"myapp\")\n        ) { callbackURL, error in\n            guard let callbackURL, error == nil,\n                  let code = URLComponents(url: callbackURL, resolvingAgainstBaseURL: false)?\n                      .queryItems?.first(where: { $0.name == \"code\" })?.value else { return }\n            Task { await self.exchangeCodeForTokens(code) }\n        }\n        session.presentationContextProvider = self\n        session.prefersEphemeralWebBrowserSession = true  // No shared cookies\n        session.start()\n    }\n\n    func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {\n        ASPresentationAnchor()\n    }\n}\n```\n\n### SwiftUI WebAuthenticationSession\n\n```swift\nstruct OAuthLoginView: View {\n    @Environment(\\.webAuthenticationSession) private var webAuthSession\n\n    var body: some View {\n        Button(\"Sign in with Provider\") {\n            Task {\n                let url = URL(string: \"https://provider.com/oauth/authorize?client_id=YOUR_ID\")!\n                let callbackURL = try await webAuthSession.authenticate(\n                    using: url, callback: .customScheme(\"myapp\")\n                )\n                // Extract authorization code from callbackURL\n            }\n        }\n    }\n}\n```\n\nCallback types: `.customScheme(\"myapp\")` for URL scheme redirects;\n`.https(host:path:)` for universal link redirects (preferred).\n\n## Password AutoFill Credentials\n\nUse `ASAuthorizationPasswordProvider` to offer saved keychain credentials\nalongside Sign in with Apple:\n\n```swift\nfunc performSignIn() {\n    let appleIDRequest = ASAuthorizationAppleIDProvider().createRequest()\n    appleIDRequest.requestedScopes = [.fullName, .email]\n\n    let passwordRequest = ASAuthorizationPasswordProvider().createRequest()\n\n    let controller = ASAuthorizationController(\n        authorizationRequests: [appleIDRequest, passwordRequest]\n    )\n    controller.delegate = self\n    controller.presentationContextProvider = self\n    controller.performRequests()\n}\n\n// In delegate:\nfunc authorizationController(\n    controller: ASAuthorizationController,\n    didCompleteWithAuthorization authorization: ASAuthorization\n) {\n    switch authorization.credential {\n    case let appleIDCredential as ASAuthorizationAppleIDCredential:\n        handleAppleIDLogin(appleIDCredential)\n    case let passwordCredential as ASPasswordCredential:\n        // User selected a saved password from keychain\n        signInWithPassword(\n            username: passwordCredential.user,\n            password: passwordCredential.password\n        )\n    default:\n        break\n    }\n}\n```\n\nSet `textContentType` on text fields for AutoFill to work:\n\n```swift\nusernameField.textContentType = .username\npasswordField.textContentType = .password\n```\n\n## Biometric Authentication\n\nUse `LAContext` from LocalAuthentication for Face ID / Touch ID as a\nsign-in or re-authentication mechanism. For protecting Keychain items\nwith biometric access control (`SecAccessControl`, `.biometryCurrentSet`),\nsee the `ios-security` skill.\n\n```swift\nimport LocalAuthentication\n\nfunc authenticateWithBiometrics() async throws -> Bool {\n    let context = LAContext()\n    var error: NSError?\n\n    guard context.canEvaluatePolicy(\n        .deviceOwnerAuthenticationWithBiometrics, error: &error\n    ) else {\n        throw AuthError.biometricsUnavailable\n    }\n\n    return try await context.evaluatePolicy(\n        .deviceOwnerAuthenticationWithBiometrics,\n        localizedReason: \"Sign in to your account\"\n    )\n}\n```\n\n**Required:** Add `NSFaceIDUsageDescription` to Info.plist. Missing this\nkey crashes on Face ID devices.\n\n## SwiftUI SignInWithAppleButton\n\n```swift\nimport AuthenticationServices\n\nstruct AppleSignInView: View {\n    @Environment(\\.colorScheme) var colorScheme\n\n    var body: some View {\n        SignInWithAppleButton(.signIn) { request in\n            request.requestedScopes = [.fullName, .email]\n        } onCompletion: { result in\n            switch result {\n            case .success(let authorization):\n                guard let credential = authorization.credential\n                    as? ASAuthorizationAppleIDCredential else { return }\n                handleCredential(credential)\n            case .failure(let error):\n                handleError(error)\n            }\n        }\n        .signInWithAppleButtonStyle(\n            colorScheme == .dark ? .white : .black\n        )\n        .frame(height: 50)\n    }\n}\n```\n\n## Common Mistakes\n\n### 1. Not checking credential state on app launch\n\n```swift\n// DON'T: Assume the user is still authorized\nfunc appDidLaunch() {\n    if UserDefaults.standard.bool(forKey: \"isLoggedIn\") {\n        showMainApp()  // User may have revoked access!\n    }\n}\n\n// DO: Check credential state every launch\nfunc appDidLaunch() async {\n    await checkCredentialState()  // See \"Credential State Checking\" above\n}\n```\n\n### 2. Not performing existing account setup flows\n\n```swift\n// DON'T: Always show a full login screen on launch\n// DO: Call performExistingAccountSetupFlows() first;\n//     show login UI only if .notInteractive error received\n```\n\n### 3. Assuming email/name are always provided\n\n```swift\n// DON'T: Force-unwrap email or fullName\nlet email = credential.email!  // Crashes on subsequent logins\n\n// DO: Handle nil gracefully -- only available on first authorization\nif let email = credential.email {\n    saveEmail(email)  // Persist immediately\n}\n```\n\n### 4. Not implementing ASAuthorizationControllerPresentationContextProviding\n\n```swift\n// DON'T: Skip the presentation context provider\ncontroller.delegate = self\ncontroller.performRequests()  // May not display UI correctly\n\n// DO: Always set the presentation context provider\ncontroller.delegate = self\ncontroller.presentationContextProvider = self  // Required for proper UI\ncontroller.performRequests()\n```\n\n### 5. Storing identityToken in UserDefaults\n\n```swift\n// DON'T: Store tokens in UserDefaults\nUserDefaults.standard.set(tokenString, forKey: \"identityToken\")\n\n// DO: Store in Keychain\n// See references/keychain-biometric.md for Keychain patterns\ntry KeychainHelper.save(tokenData, forKey: \"identityToken\")\n```\n\n## Review Checklist\n\n- [ ] \"Sign in with Apple\" capability added in Xcode project\n- [ ] `ASAuthorizationControllerPresentationContextProviding` implemented\n- [ ] Credential state checked on every app launch (`credentialState(forUserID:)`)\n- [ ] `credentialRevokedNotification` observer registered; sign-out handled\n- [ ] `email` and `fullName` cached on first authorization (not assumed available later)\n- [ ] `identityToken` sent to server for validation, not trusted client-side only\n- [ ] Tokens stored in Keychain, not UserDefaults or files\n- [ ] `performExistingAccountSetupFlows` called before showing login UI\n- [ ] Error cases handled: `.canceled`, `.failed`, `.notInteractive`\n- [ ] `NSFaceIDUsageDescription` in Info.plist for biometric auth\n- [ ] `ASWebAuthenticationSession` used for OAuth (not `WKWebView`)\n- [ ] `prefersEphemeralWebBrowserSession` set for OAuth when appropriate\n- [ ] `textContentType` set on username/password fields for AutoFill\n\n## References\n\n- Keychain & biometric patterns: [references/keychain-biometric.md](references/keychain-biometric.md)\n- [AuthenticationServices](https://sosumi.ai/documentation/authenticationservices)\n- [ASAuthorizationAppleIDProvider](https://sosumi.ai/documentation/authenticationservices/asauthorizationappleidprovider)\n- [ASAuthorizationAppleIDCredential](https://sosumi.ai/documentation/authenticationservices/asauthorizationappleidcredential)\n- [ASAuthorizationController](https://sosumi.ai/documentation/authenticationservices/asauthorizationcontroller)\n- [ASWebAuthenticationSession](https://sosumi.ai/documentation/authenticationservices/aswebauthenticationsession)\n- [ASAuthorizationPasswordProvider](https://sosumi.ai/documentation/authenticationservices/asauthorizationpasswordprovider)\n- [SignInWithAppleButton](https://sosumi.ai/documentation/authenticationservices/signinwithapplebutton)\n- [Implementing User Authentication with Sign in with Apple](https://sosumi.ai/documentation/authenticationservices/implementing-user-authentication-with-sign-in-with-apple)","tags":["authentication","swift","ios","skills","dpearson2699"],"capabilities":["skill","source-dpearson2699","category-swift-ios-skills"],"categories":["swift-ios-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/dpearson2699/swift-ios-skills/authentication","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"install_from":"skills.sh"}},"qualityScore":"0.300","qualityRationale":"deterministic score 0.30 from registry signals: · indexed on skills.sh · published under dpearson2699/swift-ios-skills","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill:v1","enrichmentVersion":1,"enrichedAt":"2026-04-22T05:40:40.076Z","embedding":null,"createdAt":"2026-04-18T20:34:18.180Z","updatedAt":"2026-04-22T05:40:40.076Z","lastSeenAt":"2026-04-22T05:40:40.076Z","tsv":"'/auth/apple':606 '/auth/keys':674 '/documentation/authenticationservices)':1427 '/documentation/authenticationservices/asauthorizationappleidcredential)':1435 '/documentation/authenticationservices/asauthorizationappleidprovider)':1431 '/documentation/authenticationservices/asauthorizationcontroller)':1439 '/documentation/authenticationservices/asauthorizationpasswordprovider)':1447 '/documentation/authenticationservices/aswebauthenticationsession)':1443 '/documentation/authenticationservices/implementing-user-authentication-with-sign-in-with-apple)':1462 '/documentation/authenticationservices/signinwithapplebutton)':1451 '/oauth/authorize?client_id=your_id':877 '/oauth/authorize?client_id=your_id&redirect_uri=myapp://callback&response_type=code':799 '0.name':826 '1':1141 '2':1186 '200':636 '3':1216 '4':1255 '5':1291 '50':1138 'access':453,526,1027,1169 'accesstoken':659 'account':59,64,460,689,1069,1190 'ad':1328 'add':105,381,1071 'allow':524 'alon':566 'alongsid':919 'alway':317,318,395,1196,1220,1276 'api':117 'api.example.com':605 'api.example.com/auth/apple':604 'app':383,447,509,740,1147,1339 'appdidlaunch':1159,1177 'appl':22,35,40,104,110,459,667,701,923,1326,1459 'appleid.apple.com':673,679 'appleid.apple.com/auth/keys':672 'appleidcredenti':962,966 'appleidrequest':714,725,928,942 'appleidrequest.requestedscopes':931 'applesigninview':1089 'application/json':610 'appropri':1410 'asauthor':174,957 'asauthorizationappleidcredenti':180,304,394,571,964,1120,1432 'asauthorizationappleidprovid':132,471,715,929,1428 'asauthorizationappleidprovider.credentialrevokednotification':536 'asauthorizationcontrol':119,141,156,171,253,723,940,954,1436 'asauthorizationcontrollerdeleg':167 'asauthorizationcontrollerpresentationcontextprovid':151,1258,1332 'asauthorizationerror':262 'asauthorizationpasswordprovid':719,913,936,1444 'aspasswordcredenti':971 'aspresentationanchor':157,848,849 'assum':1152,1217,1358 'asuserdetectionstatus':348 'aswebauthenticationpresentationcontextprovid':790 'aswebauthenticationsess':67,70,763,766,802,847,1399,1440 'aswebauthenticationsession-oauth':69 'async':468,572,1042,1178 'aud':680 'auth':25,312,314,412,781,1398 'authcod':214,241,248,249,589,622 'authcodedata':586,592 'authent':1,8,10,30,80,83,773,1001,1019,1454 'authenticatewithbiometr':1041 'authenticatewithserv':245 'authenticationservic':16,123,785,1087,1424 'autherror':259,264 'autherror.biometricsunavailable':1058 'autherror.missingtoken':597 'autherror.servervalidationfailed':639 'authmanager.shared.signout':545 'author':173,198,205,274,362,490,889,956,1114,1157,1246,1356 'authorization.credential':178,959,1118 'authorizationcod':337,621 'authorizationcontrol':169,251,952 'authorizationrequest':142,724,941 'authurl':794,804 'autofil':27,73,77,910,992,1417 'avail':409,1243,1359 'await':483,627,832,881,1061,1179 'backend':428,438 'base64':334,336 'behavior':308 'biometr':29,79,82,1000,1026,1397,1420 'biometric-authent':81 'biometrycurrentset':1030 'black':1135 'bodi':862,1096 'bool':1044 'break':268,288,985 'bundl':683 'button':865 'cach':363,403,1353 'call':734,1205,1382 'callback':805,885,893 'callbackurl':808,813,820,879,892 'cancel':267,1390 'capabl':111,1327 'case':266,271,276,281,286,489,492,504,507,960,967,1111,1125,1388 'catch':521 'category-swift-ios-skills' 'check':48,52,230,297,441,442,695,1143,1171,1184,1336 'checkcredentialst':467,1180 'checklist':95,98,1322 'class':125,787 'clear':499 'client':564,1370 'client-sid':563,1369 'code':219,265,342,346,817,827,834,890 'colorschem':1092,1094,1132 'common':89,92,1139 'common-mistak':91 'content':31,613 'content-typ':612 'context':1046,1265,1280 'context.canevaluatepolicy':1052 'context.evaluatepolicy':1062 'control':140,155,170,252,722,939,953,1028 'controller.delegate':144,727,944,1267,1282 'controller.performrequests':148,731,948,1269,1290 'controller.presentationcontextprovider':146,729,946,1284 'cooki':841 'correct':1274 'crash':1078,1234 'createrequest':716,720,930,937 'credenti':41,44,46,50,74,78,177,228,302,393,439,443,530,570,704,745,911,918,1117,1124,1144,1172,1182,1334 'credential-handl':43 'credential-state-check':49 'credential.authorizationcode':215,587 'credential.email':194,432,1233,1250 'credential.fullname':201,416 'credential.identitytoken':208,577 'credential.user':185,402 'credentialrevokednotif':1343 'credentialst':1341 'critic':352 'customschem':806,886,895 'dark':1133 'data':332,338,501,581,591,624,647 'decod':644 'default':298,519,984 'deleg':159,749,950 'delet':377 'devic':1082 'deviceownerauthenticationwithbiometr':1053,1063 'didcompletewithauthor':172,955 'didcompletewitherror':254 'dismiss':270 'display':1272 'dpearson2699':7 'els':181,476,595,637,829,1056,1121 'email':138,193,319,353,406,431,434,933,1105,1228,1232,1249,1252,1350 'email/name':1218 'encod':583,593,618 'environ':856,1091 'error':255,257,260,301,523,753,809,814,1049,1054,1055,1128,1130,1214,1387 'etc':776 'everi':446,1174,1338 'exchang':222 'exist':58,63,688,697,744,1189 'existing-account-setup-flow':62 'exp':685 'expect':294 'extens':149,165 'extract':888 'face':1007,1080 'fail':272,275,293,1391 'failur':163,1126 'fals':822 'field':990,1415 'file':1380 'final':124,786 'first':197,204,311,361,411,824,1207,1245,1355 'flow':11,61,66,372,691,782,1192 'forc':1226 'force-unwrap':1225 'forhttpheaderfield':611 'forkey':658,1162,1305,1319 'fornam':535 'foruserid':485,1342 'found':747 'frame':1136 'framework':17 'full':1199 'fullnam':137,200,325,355,415,422,932,1104,1230,1352 'func':128,152,168,250,391,466,568,711,791,843,925,951,1040,1158,1176 'github':775 'googl':774 'grace':1241 'guard':175,472,574,631,811,1051,1115 'handl':42,45,160,285,303,754,1239,1349,1389 'handleappleidlogin':965 'handlecredenti':392,1123 'handleerror':1129 'height':1137 'host':902 'https':901 'httpurlrespons':634 'id':684,1008,1010,1081 'identifi':191,399,516 'identitytoken':207,239,246,247,331,549,619,1293,1306,1320,1361 'immedi':365,407,544,1254 'implement':9,1257,1333,1452 'import':122,784,1038,1086 'includ':18 'info.plist':1074,1395 'initi':368 'interact':291 'invalid':279 'invalidrespons':277 'io':3,13,1034 'ios-secur':1033 'isloggedin':1163 'iss':677 'item':1024 'jsondecod':643 'jsonencod':617 'jwks':675 'jwt':209,333,335,552,665 'key':670,1077 'keychain':226,234,652,917,978,1023,1310,1314,1376,1419 'keychainhelper.save':656,1317 'lacontext':1003,1047 'later':376,1360 'launch':448,693,741,1148,1175,1203,1340 'let':130,133,139,176,183,192,199,206,213,258,400,414,417,430,469,473,480,575,578,585,588,623,640,713,717,721,793,800,812,816,871,878,927,934,938,961,968,1045,1113,1116,1127,1231,1248 'likelyr':349 'link':906 'live':218,341,345 'loadsaveduserid':475 'local':500 'localauthent':1005,1039 'localizedreason':1064 'login':708,761,1200,1209,1237,1385 'loginviewcontrol':126,150,166 'main':540 'match':681 'may':451,1166,1270 'mechan':1020 'migrat':514 'migrateus':517 'miss':1075 'mistak':90,93,1140 'myapp':807,887,896 'name':404,418,424 'network':522 'never':560,777 'new':512 'nil':195,202,324,330,538,815,1240 'non':290 'non-interact':289 'normal':760 'notfound':505 'nothandl':282 'notif':532 'notificationcenter.default.addobserver':534 'notinteract':287,752,1213,1392 'nserror':1050 'nsfaceidusagedescript':1072,1393 'nsobject':789 'oauth':68,71,764,768,1402,1408 'oauth/third-party':23 'oauthcontrol':788 'oauthloginview':854 'object':537 'observ':1344 'offer':915 'offlin':525 'oncomplet':1106 'option':732 'parti':772 'pass':687 'password':26,72,76,703,909,976,982,999 'password-autofill-credenti':75 'passwordcredenti':969 'passwordcredential.password':983 'passwordcredential.user':981 'passwordfield.textcontenttype':998 'passwordrequest':718,726,935,943 'path':903 'pattern':235,1315,1421 'per':189 'per-team':188 'perform':1188 'performexistingaccountsetupflow':712,1206,1381 'performsignin':926 'persist':396,425,435,1253 'personnamecompon':326 'personnamecomponentsformatt':419 'post':608 'prefer':908 'preferimmediatelyavailablecredenti':733 'prefersephemeralwebbrowsersess':1405 'present':1264,1279 'presentationanchor':153,844 'privat':858 'proceedtomainapp':491,529 'project':1331 'proper':1288 'properti':305,309 'protect':1022 'provid':131,321,327,357,470,869,1221,1266,1281 'provider.com':798,876 'provider.com/oauth/authorize?client_id=your_id':875 'provider.com/oauth/authorize?client_id=your_id&redirect_uri=myapp://callback&response_type=code':797 'provider.createrequest':135 'provider.credentialstate':484 'public':669 'queryitem':823 'queue':539 're':380,1018 're-add':379 're-authent':1017 'realuserstatus':347 'receiv':750,1215 'redirect':900,907 'refer':99,100,1418 'references/keychain-biometric.md':232,654,1312,1422,1423 'regist':1345 'request':134,143,292,323,329,599,630,1101 'request.httpbody':615 'request.httpmethod':607 'request.requestedscopes':136,1103 'request.setvalue':609 'requir':1070,1286 'resolvingagainstbaseurl':821 'respons':280,625,632 'result':1107,1110 'retri':528 'return':182,389,478,830,1059,1122 'review':94,97,1321 'review-checklist':96 'revoc':531 'revok':452,493,495,1168 'save':223,916,975 'saveemail':1251 'saveuserid':236 'scheme':899 'screen':709,1201 'secaccesscontrol':1029 'secur':464,1035 'see':231,653,1031,1181,1311 'select':973 'self':145,147,728,730,836,945,947,1268,1283,1285 'self.exchangecodefortokens':833 'send':238,553 'sendtokentoserv':569 'sent':1362 'server':211,221,244,557,661,1364 'server-sid':660 'session':641,649,801,846 'session.accesstoken':657 'session.prefersephemeralwebbrowsersession':837 'session.presentationcontextprovider':835 'session.start':842 'sessionresponse.self':645 'set':458,986,1277,1406,1412 'setup':60,65,120,690,1191 'share':840 'short':217,340,344 'short-liv':216,339,343 'show':706,758,1197,1208,1384 'showerror':273,278,283,299 'showloginscreen':477,503,506,520 'showmainapp':1164 'side':565,662,1371 'sign':19,32,37,101,107,370,462,496,542,698,866,920,1014,1065,1323,1347,1456 'sign-in':461,1013 'sign-in-with-appl':36 'sign-out':1346 'sign-up':369 'signin':1100 'signinwithapplebutton':85,88,1084,1099,1448 'signinwithapplebuttonstyl':1131 'signinwithpassword':979 'signout':502 'silent':296,694,756 'skill':4,5,1036 'skip':1262 'sosumi.ai':1426,1430,1434,1438,1442,1446,1450,1461 'sosumi.ai/documentation/authenticationservices)':1425 'sosumi.ai/documentation/authenticationservices/asauthorizationappleidcredential)':1433 'sosumi.ai/documentation/authenticationservices/asauthorizationappleidprovider)':1429 'sosumi.ai/documentation/authenticationservices/asauthorizationcontroller)':1437 'sosumi.ai/documentation/authenticationservices/asauthorizationpasswordprovider)':1445 'sosumi.ai/documentation/authenticationservices/aswebauthenticationsession)':1441 'sosumi.ai/documentation/authenticationservices/implementing-user-authentication-with-sign-in-with-apple)':1460 'sosumi.ai/documentation/authenticationservices/signinwithapplebutton)':1449 'source-dpearson2699' 'stabl':186 'startoauthflow':792 'startsigninwithappl':129 'state':47,51,229,440,444,481,488,1145,1173,1183,1335 'statuscod':635 'still':1156 'store':648,1292,1299,1308,1374 'string':316,320,420,580,590,603,796,874 'struct':853,1088 'subsequ':313,1236 'success':161,1112 'swift':2,121,164,390,465,533,567,710,783,852,924,995,1037,1085,1149,1193,1222,1259,1296 'swiftui':84,87,850,1083 'swiftui-signinwithapplebutton':86 'switch':263,487,958,1109 'task':831,870 'team':190,513 'text':989 'textcontenttyp':987,1411 'third':771 'third-parti':770 'throw':573,596,638,1043,1057 'time':456 'token':53,56,546,579,620,650,1300,1373 'token-valid':55 'tokendata':576,582,1318 'tokenstr':1304 'touch':1009 'transfer':508,510 'tri':482,616,626,642,655,880,1060,1316 'true':838 'trust':561,1368 'type':310,614,894 'ui':762,1210,1273,1289,1386 'uikit':118 'uiviewcontrol':127 'uniqu':187 'univers':905 'unknown':300,350,351,518 'unwrap':1227 'url':601,602,795,803,819,872,873,884,898 'urlcompon':818 'urlrequest':600 'urlsession.shared.data':628 'use':14,115,765,778,883,912,1002,1400 'user':269,315,375,398,450,494,515,972,1154,1165,1453 'userdefault':1295,1302,1378 'userdefaults.standard.bool':1161 'userdefaults.standard.set':1303 'userid':184,224,237,401,474,486 'usernam':980,997 'username/password':1414 'usernamefield.textcontenttype':996 'userprofile.saveemail':433 'userprofile.savename':423 'utf8':584,594 'valid':54,57,212,547,559,663,1366 'valu':385,828 'var':598,859,861,1048,1093,1095 'verifi':676 'via':457 'view':855,864,1090,1098 'view.window':158 'viewdidappear':737 'web':24 'webauthenticationsess':851,857 'webauthsess':860 'webauthsession.authenticate':882 'white':1134 'wkwebview':779,1404 'work':994 'xcode':113,1330","prices":[{"id":"cc567a17-4136-4757-8022-2db0670cf27b","listingId":"15d9ee11-2f18-4898-84da-301a8d181042","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"dpearson2699","category":"swift-ios-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T20:34:18.180Z"}],"sources":[{"listingId":"15d9ee11-2f18-4898-84da-301a8d181042","source":"github","sourceId":"dpearson2699/swift-ios-skills/authentication","sourceUrl":"https://github.com/dpearson2699/swift-ios-skills/tree/main/skills/authentication","isPrimary":false,"firstSeenAt":"2026-04-18T22:00:45.664Z","lastSeenAt":"2026-04-22T00:53:41.613Z"},{"listingId":"15d9ee11-2f18-4898-84da-301a8d181042","source":"skills_sh","sourceId":"dpearson2699/swift-ios-skills/authentication","sourceUrl":"https://skills.sh/dpearson2699/swift-ios-skills/authentication","isPrimary":true,"firstSeenAt":"2026-04-18T20:34:18.180Z","lastSeenAt":"2026-04-22T05:40:40.076Z"}],"details":{"listingId":"15d9ee11-2f18-4898-84da-301a8d181042","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"dpearson2699","slug":"authentication","source":"skills_sh","category":"swift-ios-skills","skills_sh_url":"https://skills.sh/dpearson2699/swift-ios-skills/authentication"},"updatedAt":"2026-04-22T05:40:40.076Z"}}