@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +266 -267
- package/package.json +2 -2
- package/pipeline/scripts/_notices.mjs +1 -1
- package/pipeline/skills/.skill-manifest.json +68 -68
- package/pipeline/skills/shared/README.md +70 -70
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
- package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
- package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
- package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
- package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
- package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
- package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
- package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
- package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
- package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
- package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
- package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
- package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
- package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
- package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
- package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
- package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
- package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
- package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
- package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
- package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
- package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
- package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
- package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
- package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
- package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
- package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
- package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
- package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
- package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
- package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
- package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
- package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
- package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
- package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
- package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
- package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
- package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
- package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
- package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
- package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
- package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
- package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
- package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
- package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
- package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
- package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
- package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
- package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
- package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
- package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
- package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
- package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
- package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
- package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
- package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
- package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
- package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
- package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
- package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
- package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
- package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
- package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
- package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
- package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
- package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
- package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
- package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
- package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
- package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
- package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
- package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
- package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
- package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
- package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
- package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
- package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
- package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
- package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
- package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
- package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
- package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
- package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
- package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
- package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
- package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
- package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
- package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
- package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
- package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
- package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
- package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
- package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
- package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
- package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
- package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
- package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
- package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
- package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
- package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
- package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
- package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
- package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
- package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
- package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
- package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
- package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
- package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
- package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
- package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
- package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
- package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
- package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
- package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
- package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
- package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
- package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
- package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
- package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
- package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
- package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
- package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
- package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
- package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
- package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
- package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
- package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
- package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
- package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
- package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
- package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
- package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
- package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
- package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
- package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
- package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
- package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
- package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
- package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
- package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
- package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
- package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
- package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
- package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
- package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
- package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
- package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
- package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
- package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
- package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
- package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
- package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
- package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
- package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
- package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
- package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
- package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
- package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
- package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
- package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
- package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
- package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
- package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
- package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
- package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
- package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
- package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
- package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
- package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
- package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
- package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
- package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
- package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
- package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
- package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
- package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
- package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
- package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
- package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
- package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
- package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
- package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
- package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
- package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
- package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
|
@@ -1,46 +1,35 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: authentication
|
|
3
|
-
description: "
|
|
3
|
+
description: "iOS sign-in with AuthenticationServices and LocalAuthentication: Sign in with Apple, passkey (WebAuthn) registration and login via ASAuthorizationPlatformPublicKeyCredentialProvider, ASAuthorizationController credential state and revocation, ASWebAuthenticationSession OAuth and third-party login, Password AutoFill, server-side identity-token validation, LAContext biometric re-authentication. Use when adding or reviewing account flows."
|
|
4
4
|
metadata:
|
|
5
|
-
source:
|
|
5
|
+
source: multi-agent-pipeline
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Authentication
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- [
|
|
18
|
-
- [
|
|
19
|
-
- [Token Validation](#token-validation)
|
|
20
|
-
- [Existing Account Setup Flows](#existing-account-setup-flows)
|
|
21
|
-
- [Passkeys](#passkeys)
|
|
22
|
-
- [ASWebAuthenticationSession (OAuth)](#aswebauthenticationsession-oauth)
|
|
23
|
-
- [Password AutoFill Credentials](#password-autofill-credentials)
|
|
24
|
-
- [Biometric Authentication](#biometric-authentication)
|
|
25
|
-
- [Security Boundaries](#security-boundaries)
|
|
26
|
-
- [SwiftUI SignInWithAppleButton](#swiftui-signinwithapplebutton)
|
|
27
|
-
- [Common Mistakes](#common-mistakes)
|
|
28
|
-
- [Review Checklist](#review-checklist)
|
|
29
|
-
- [References](#references)
|
|
10
|
+
This skill owns the account-facing side of identity on iOS: Sign in with
|
|
11
|
+
Apple, passkeys, Password AutoFill, `ASAuthorizationController`, presenting an
|
|
12
|
+
OAuth session with `ASWebAuthenticationSession`, credential state, and local
|
|
13
|
+
biometric re-authentication. Code samples assume iOS 26 with Swift 6.3.
|
|
14
|
+
|
|
15
|
+
Deeper material:
|
|
16
|
+
|
|
17
|
+
- [Passkeys](references/passkeys.md) - prerequisites, registration, sign-in, AutoFill, security keys, failure modes
|
|
18
|
+
- [Keychain and biometrics](references/keychain-biometric.md) - token storage, `LAContext`, access-controlled items
|
|
30
19
|
|
|
31
20
|
## Sign in with Apple
|
|
32
21
|
|
|
33
|
-
|
|
22
|
+
Before any code, add the Apple sign-in capability to the app target in Signing & Capabilities.
|
|
34
23
|
|
|
35
|
-
###
|
|
24
|
+
### Starting the request (UIKit)
|
|
36
25
|
|
|
37
26
|
```swift
|
|
38
27
|
import AuthenticationServices
|
|
28
|
+
import UIKit
|
|
39
29
|
|
|
40
|
-
final class
|
|
41
|
-
func
|
|
42
|
-
let
|
|
43
|
-
let request = provider.createRequest()
|
|
30
|
+
final class WelcomeViewController: UIViewController {
|
|
31
|
+
func beginAppleSignIn() {
|
|
32
|
+
let request = ASAuthorizationAppleIDProvider().createRequest()
|
|
44
33
|
request.requestedScopes = [.fullName, .email]
|
|
45
34
|
|
|
46
35
|
let controller = ASAuthorizationController(authorizationRequests: [request])
|
|
@@ -50,445 +39,340 @@ final class LoginViewController: UIViewController {
|
|
|
50
39
|
}
|
|
51
40
|
}
|
|
52
41
|
|
|
53
|
-
extension
|
|
42
|
+
extension WelcomeViewController: ASAuthorizationControllerPresentationContextProviding {
|
|
54
43
|
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
|
|
55
|
-
view.window
|
|
44
|
+
guard let window = view.window else {
|
|
45
|
+
preconditionFailure("Start Sign in with Apple only while this screen is on screen")
|
|
46
|
+
}
|
|
47
|
+
return window // ASPresentationAnchor() with no scene is deprecated in iOS 26
|
|
56
48
|
}
|
|
57
49
|
}
|
|
58
50
|
```
|
|
59
51
|
|
|
60
|
-
### Delegate
|
|
52
|
+
### Delegate callbacks
|
|
61
53
|
|
|
62
54
|
```swift
|
|
63
|
-
extension
|
|
64
|
-
func authorizationController(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
let email = credential.email // nil after first authorization
|
|
73
|
-
let fullName = credential.fullName // nil after first authorization
|
|
74
|
-
let identityToken = credential.identityToken // JWT for server validation
|
|
75
|
-
let authCode = credential.authorizationCode // Short-lived code for server exchange
|
|
76
|
-
|
|
77
|
-
// Save userID to Keychain for credential state checks
|
|
78
|
-
// See references/keychain-biometric.md for Keychain patterns
|
|
79
|
-
saveUserID(userID)
|
|
80
|
-
|
|
81
|
-
// Send identityToken and authCode to your server
|
|
82
|
-
authenticateWithServer(identityToken: identityToken, authCode: authCode)
|
|
55
|
+
extension WelcomeViewController: ASAuthorizationControllerDelegate {
|
|
56
|
+
func authorizationController(controller: ASAuthorizationController,
|
|
57
|
+
didCompleteWithAuthorization authorization: ASAuthorization) {
|
|
58
|
+
let credential = authorization.credential
|
|
59
|
+
guard let apple = credential as? ASAuthorizationAppleIDCredential else { return }
|
|
60
|
+
AccountVault.shared.rememberAppleUser(apple.user) // stable per-team ID
|
|
61
|
+
if let name = apple.fullName { ProfileDraft.shared.name = name } // first time only
|
|
62
|
+
if let mail = apple.email { ProfileDraft.shared.email = mail } // first time only
|
|
63
|
+
Task { try await SessionAPI.exchange(token: apple.identityToken, code: apple.authorizationCode) }
|
|
83
64
|
}
|
|
84
65
|
|
|
85
|
-
func authorizationController(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
case .
|
|
92
|
-
|
|
93
|
-
case .failed:
|
|
94
|
-
showError("Authorization failed")
|
|
95
|
-
case .invalidResponse:
|
|
96
|
-
showError("Invalid response")
|
|
97
|
-
case .notHandled:
|
|
98
|
-
showError("Not handled")
|
|
99
|
-
case .notInteractive:
|
|
100
|
-
break // Non-interactive request failed -- expected for silent checks
|
|
66
|
+
func authorizationController(controller: ASAuthorizationController,
|
|
67
|
+
didCompleteWithError error: Error) {
|
|
68
|
+
guard let authError = error as? ASAuthorizationError else { return }
|
|
69
|
+
switch authError.code {
|
|
70
|
+
case .canceled: break // user closed the sheet
|
|
71
|
+
case .notInteractive: break // silent check found nothing
|
|
72
|
+
case .failed, .invalidResponse, .notHandled:
|
|
73
|
+
showSignInProblem(authError)
|
|
101
74
|
default:
|
|
102
|
-
|
|
75
|
+
showSignInProblem(authError)
|
|
103
76
|
}
|
|
104
77
|
}
|
|
105
78
|
}
|
|
106
79
|
```
|
|
107
80
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
`
|
|
81
|
+
`user` is a stable identifier scoped to your team; save it in the Keychain so
|
|
82
|
+
you can check credential state later. `identityToken` is a JWT for the server
|
|
83
|
+
and `authorizationCode` is a short-lived code the server exchanges; send both.
|
|
111
84
|
|
|
112
|
-
|
|
113
|
-
|---|---|---|---|
|
|
114
|
-
| `user` | `String` | Always | Always |
|
|
115
|
-
| `email` | `String?` | Provided if requested | `nil` |
|
|
116
|
-
| `fullName` | `PersonNameComponents?` | Provided if requested | `nil` |
|
|
117
|
-
| `identityToken` | `Data?` | JWT encoded as UTF-8 data | JWT encoded as UTF-8 data |
|
|
118
|
-
| `authorizationCode` | `Data?` | Short-lived code | Short-lived code |
|
|
119
|
-
| `realUserStatus` | `ASUserDetectionStatus` | Fraud-prevention signal | Do not rely on later attempts |
|
|
85
|
+
### What the credential contains
|
|
120
86
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
user
|
|
87
|
+
| Property | Type | Availability |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| `user` | `String` | Every time |
|
|
90
|
+
| `email` | `String?` | First authorization only, if requested |
|
|
91
|
+
| `fullName` | `PersonNameComponents?` | First authorization only, if requested |
|
|
92
|
+
| `identityToken` | `Data?` | Every time, a UTF-8 JWT |
|
|
93
|
+
| `authorizationCode` | `Data?` | Every time, short-lived |
|
|
94
|
+
| `realUserStatus` | `ASUserDetectionStatus` | Anti-fraud hint; do not count on it after the first attempt |
|
|
124
95
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
// Cache name and email IMMEDIATELY -- only available on first auth
|
|
131
|
-
if let fullName = credential.fullName {
|
|
132
|
-
let name = PersonNameComponentsFormatter().string(from: fullName)
|
|
133
|
-
UserProfile.saveName(name) // Persist to your backend
|
|
134
|
-
}
|
|
135
|
-
if let email = credential.email {
|
|
136
|
-
UserProfile.saveEmail(email) // Persist to your backend
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
```
|
|
96
|
+
Capture name and email the moment they arrive and send them to your backend.
|
|
97
|
+
Apple never sends them again, and deleting and reinstalling the app does not
|
|
98
|
+
bring them back. Format a name with
|
|
99
|
+
`PersonNameComponentsFormatter().string(from:)` before storing it.
|
|
140
100
|
|
|
141
|
-
|
|
101
|
+
### Credential state
|
|
142
102
|
|
|
143
|
-
|
|
144
|
-
|
|
103
|
+
People can revoke the app at any time under Settings > Apple Account >
|
|
104
|
+
Sign-In & Security, so check on every launch:
|
|
145
105
|
|
|
146
106
|
```swift
|
|
147
|
-
func
|
|
107
|
+
func verifyAppleCredential(for userID: String) {
|
|
148
108
|
let provider = ASAuthorizationAppleIDProvider()
|
|
149
|
-
|
|
150
|
-
showLoginScreen()
|
|
151
|
-
return
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
provider.getCredentialState(forUserID: userID) { state, _ in
|
|
109
|
+
provider.getCredentialState(forUserID: userID) { credentialState, _ in
|
|
155
110
|
DispatchQueue.main.async {
|
|
156
|
-
switch
|
|
157
|
-
case .authorized:
|
|
158
|
-
|
|
159
|
-
case .
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
showLoginScreen()
|
|
163
|
-
case .notFound:
|
|
164
|
-
showLoginScreen()
|
|
165
|
-
case .transferred:
|
|
166
|
-
// App transferred to new team -- migrate user identifier
|
|
167
|
-
migrateUser()
|
|
168
|
-
@unknown default:
|
|
169
|
-
showLoginScreen()
|
|
111
|
+
switch credentialState {
|
|
112
|
+
case .authorized: SessionStore.shared.resume()
|
|
113
|
+
case .revoked: SessionStore.shared.signOutAndWipe()
|
|
114
|
+
case .notFound: SessionStore.shared.presentLogin()
|
|
115
|
+
case .transferred: SessionStore.shared.migrateTransferredUser(userID)
|
|
116
|
+
@unknown default: SessionStore.shared.presentLogin()
|
|
170
117
|
}
|
|
171
118
|
}
|
|
172
119
|
}
|
|
173
120
|
}
|
|
174
121
|
```
|
|
175
122
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
forName: ASAuthorizationAppleIDProvider.credentialRevokedNotification,
|
|
181
|
-
object: nil,
|
|
182
|
-
queue: .main
|
|
183
|
-
) { _ in
|
|
184
|
-
// Sign out immediately
|
|
185
|
-
AuthManager.shared.signOut()
|
|
186
|
-
}
|
|
187
|
-
```
|
|
123
|
+
`.transferred` means the app moved to a different developer team and the user
|
|
124
|
+
identifier must be migrated. Also subscribe to
|
|
125
|
+
`ASAuthorizationAppleIDProvider.credentialRevokedNotification` and sign the
|
|
126
|
+
user out as soon as it fires.
|
|
188
127
|
|
|
189
|
-
|
|
128
|
+
### Server validation
|
|
190
129
|
|
|
191
|
-
|
|
192
|
-
|
|
130
|
+
Never accept the identity token on the device alone. Hand both values to your
|
|
131
|
+
backend and keep only what it returns:
|
|
193
132
|
|
|
194
133
|
```swift
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
134
|
+
enum SessionAPI {
|
|
135
|
+
struct Session: Decodable { let accessToken: String }
|
|
136
|
+
|
|
137
|
+
static func exchange(token: Data?, code: Data?) async throws {
|
|
138
|
+
guard let token, let code,
|
|
139
|
+
let jwt = String(data: token, encoding: .utf8),
|
|
140
|
+
let grant = String(data: code, encoding: .utf8) else { throw URLError(.badServerResponse) }
|
|
141
|
+
|
|
142
|
+
guard let endpoint = URL(string: "https://api.tasknest.app/auth/apple") else { throw URLError(.badURL) }
|
|
143
|
+
var call = URLRequest(url: endpoint)
|
|
144
|
+
call.httpMethod = "POST"
|
|
145
|
+
call.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
146
|
+
let body = try JSONEncoder().encode(["identity_token": jwt, "authorization_code": grant])
|
|
147
|
+
let (bytes, reply) = try await URLSession(configuration: .ephemeral).upload(for: call, from: body)
|
|
148
|
+
let status = (reply as? HTTPURLResponse)?.statusCode ?? 0
|
|
149
|
+
if status != 200 { throw URLError(.userAuthenticationRequired) }
|
|
150
|
+
|
|
151
|
+
let session = try JSONDecoder().decode(Session.self, from: bytes)
|
|
152
|
+
try TokenVault(service: "auth").store(Data(session.accessToken.utf8), account: "access")
|
|
201
153
|
}
|
|
202
|
-
|
|
203
|
-
var request = URLRequest(url: URL(string: "https://api.example.com/auth/apple")!)
|
|
204
|
-
request.httpMethod = "POST"
|
|
205
|
-
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
206
|
-
request.httpBody = try JSONEncoder().encode(
|
|
207
|
-
["identityToken": token, "authorizationCode": authCode]
|
|
208
|
-
)
|
|
209
|
-
|
|
210
|
-
let (data, response) = try await URLSession.shared.data(for: request)
|
|
211
|
-
guard (response as? HTTPURLResponse)?.statusCode == 200 else {
|
|
212
|
-
throw AuthError.serverValidationFailed
|
|
213
|
-
}
|
|
214
|
-
let session = try JSONDecoder().decode(SessionResponse.self, from: data)
|
|
215
|
-
// Store session token in Keychain -- see references/keychain-biometric.md
|
|
216
|
-
try KeychainHelper.save(session.accessToken, forKey: "accessToken")
|
|
217
154
|
}
|
|
218
155
|
```
|
|
219
156
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
`
|
|
157
|
+
`TokenVault` is in the [Keychain reference](references/keychain-biometric.md). The
|
|
158
|
+
server verifies the JWT signature against Apple's published key set (the JWKS
|
|
159
|
+
document served from `appleid.apple.com` under `/auth/keys`) and checks that `iss` is
|
|
160
|
+
`https://appleid.apple.com`, `aud` is your bundle ID, and `exp` is in the future.
|
|
223
161
|
|
|
224
|
-
|
|
162
|
+
### Existing accounts first
|
|
225
163
|
|
|
226
|
-
|
|
227
|
-
|
|
164
|
+
Before showing a login screen, quietly ask the system whether the person
|
|
165
|
+
already has an Apple ID credential or a saved password for the app:
|
|
228
166
|
|
|
229
167
|
```swift
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
)
|
|
168
|
+
extension WelcomeViewController {
|
|
169
|
+
func offerSavedCredentials() {
|
|
170
|
+
let requests: [ASAuthorizationRequest] = [
|
|
171
|
+
ASAuthorizationAppleIDProvider().createRequest(),
|
|
172
|
+
ASAuthorizationPasswordProvider().createRequest(),
|
|
173
|
+
]
|
|
174
|
+
let controller = ASAuthorizationController(authorizationRequests: requests)
|
|
175
|
+
controller.delegate = self
|
|
176
|
+
controller.presentationContextProvider = self
|
|
177
|
+
controller.performRequests(options: .preferImmediatelyAvailableCredentials)
|
|
178
|
+
}
|
|
242
179
|
}
|
|
243
180
|
```
|
|
244
181
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
## Passkeys
|
|
250
|
-
|
|
251
|
-
Use passkeys for passwordless WebAuthn-style registration and sign-in. The
|
|
252
|
-
app must have an Associated Domains entitlement for the relying party domain
|
|
253
|
-
using the `webcredentials:` service; passkey requests fail for services the app
|
|
254
|
-
has not configured as associated domains.
|
|
182
|
+
Run it from `viewDidAppear` or during launch. When nothing is stored, the
|
|
183
|
+
delegate receives `.notInteractive`; treat that silently and show the usual
|
|
184
|
+
login.
|
|
255
185
|
|
|
256
|
-
|
|
257
|
-
challenge and create requests with `ASAuthorizationPlatformPublicKeyCredentialProvider`:
|
|
186
|
+
### SwiftUI button
|
|
258
187
|
|
|
259
188
|
```swift
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
let request = provider.createCredentialAssertionRequest(challenge: challenge)
|
|
283
|
-
|
|
284
|
-
switch authorization.credential {
|
|
285
|
-
case let registration as ASAuthorizationPlatformPublicKeyCredentialRegistration:
|
|
286
|
-
try await server.finishPasskeyRegistration(registration)
|
|
287
|
-
case let assertion as ASAuthorizationPlatformPublicKeyCredentialAssertion:
|
|
288
|
-
try await server.finishPasskeySignIn(assertion)
|
|
289
|
-
default:
|
|
290
|
-
break
|
|
189
|
+
import AuthenticationServices
|
|
190
|
+
import SwiftUI
|
|
191
|
+
|
|
192
|
+
struct AppleSignInRow: View {
|
|
193
|
+
let model: LoginModel
|
|
194
|
+
|
|
195
|
+
var body: some View {
|
|
196
|
+
SignInWithAppleButton(.continue) { request in
|
|
197
|
+
request.requestedScopes = [.fullName, .email]
|
|
198
|
+
} onCompletion: { outcome in
|
|
199
|
+
switch outcome {
|
|
200
|
+
case .success(let auth):
|
|
201
|
+
if let apple = auth.credential as? ASAuthorizationAppleIDCredential {
|
|
202
|
+
model.completeAppleSignIn(apple) // same server validation as UIKit
|
|
203
|
+
}
|
|
204
|
+
case .failure(let error):
|
|
205
|
+
model.report(error)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
.signInWithAppleButtonStyle(.whiteOutline)
|
|
209
|
+
.frame(height: 48)
|
|
210
|
+
}
|
|
291
211
|
}
|
|
292
212
|
```
|
|
293
213
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
`performAutoFillAssistedRequests()`. Use `ASAuthorizationSecurityKeyPublicKeyCredentialProvider`
|
|
297
|
-
only when the user must authenticate with a physical security key. See
|
|
298
|
-
[references/passkeys.md](references/passkeys.md) for complete registration,
|
|
299
|
-
assertion, AutoFill, and security-key patterns.
|
|
214
|
+
The button lives in AuthenticationServices but needs SwiftUI imported in the
|
|
215
|
+
same file.
|
|
300
216
|
|
|
301
|
-
##
|
|
217
|
+
## Passkeys
|
|
302
218
|
|
|
303
|
-
|
|
304
|
-
|
|
219
|
+
Passkeys replace passwords with WebAuthn credentials for both account creation and login. They need
|
|
220
|
+
the Associated Domains entitlement with a `webcredentials:` entry for the
|
|
221
|
+
relying-party domain; requests for a domain that is not associated fail.
|
|
222
|
+
Platform passkeys sync through iCloud Keychain.
|
|
305
223
|
|
|
306
|
-
|
|
307
|
-
|
|
224
|
+
- Create `ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier:)`.
|
|
225
|
+
- Register with `createCredentialRegistrationRequest(challenge:name:userID:)` using a
|
|
226
|
+
challenge and user ID `Data` from the server.
|
|
227
|
+
- Sign in with `createCredentialAssertionRequest(challenge:)` and a fresh server challenge.
|
|
228
|
+
- Results arrive as `ASAuthorizationPlatformPublicKeyCredentialRegistration` or
|
|
229
|
+
`ASAuthorizationPlatformPublicKeyCredentialAssertion`; the relying-party server verifies them.
|
|
230
|
+
- For inline suggestions, mark the username field `.username`, include an
|
|
231
|
+
assertion request, and call `performAutoFillAssistedRequests()`.
|
|
232
|
+
- Reach for `ASAuthorizationSecurityKeyPublicKeyCredentialProvider` only when a
|
|
233
|
+
physical security key is required.
|
|
308
234
|
|
|
309
|
-
|
|
310
|
-
private weak var presentationAnchor: ASPresentationAnchor?
|
|
235
|
+
Details and code: [passkeys reference](references/passkeys.md).
|
|
311
236
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
237
|
+
## OAuth with `ASWebAuthenticationSession`
|
|
238
|
+
|
|
239
|
+
Third-party logins (Google, GitHub and similar) go through
|
|
240
|
+
`ASWebAuthenticationSession`, never an embedded `WKWebView`.
|
|
315
241
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
guard
|
|
324
|
-
let
|
|
325
|
-
|
|
326
|
-
Task { await
|
|
242
|
+
```swift
|
|
243
|
+
final class GitHubLogin: NSObject, ASWebAuthenticationPresentationContextProviding {
|
|
244
|
+
private var session: ASWebAuthenticationSession?
|
|
245
|
+
|
|
246
|
+
func start(authorizeURL: URL) {
|
|
247
|
+
let session = ASWebAuthenticationSession(url: authorizeURL,
|
|
248
|
+
callback: .customScheme("tasknest")) { url, error in
|
|
249
|
+
guard error == nil, let url,
|
|
250
|
+
let items = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems,
|
|
251
|
+
let code = items.first(where: { $0.name == "code" })?.value else { return }
|
|
252
|
+
Task { try await OAuthBackend.redeem(code: code) }
|
|
327
253
|
}
|
|
328
254
|
session.presentationContextProvider = self
|
|
329
|
-
session.prefersEphemeralWebBrowserSession = true
|
|
255
|
+
session.prefersEphemeralWebBrowserSession = true // no shared browser cookies
|
|
330
256
|
session.start()
|
|
257
|
+
self.session = session
|
|
331
258
|
}
|
|
332
259
|
|
|
333
|
-
func presentationAnchor(for
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
return
|
|
260
|
+
func presentationAnchor(for webSession: ASWebAuthenticationSession) -> ASPresentationAnchor {
|
|
261
|
+
let scenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }
|
|
262
|
+
if let key = scenes.compactMap(\.keyWindow).first { return key }
|
|
263
|
+
guard let scene = scenes.first else { preconditionFailure("No window scene to present from") }
|
|
264
|
+
return ASPresentationAnchor(windowScene: scene)
|
|
338
265
|
}
|
|
339
266
|
}
|
|
340
267
|
```
|
|
341
268
|
|
|
342
|
-
In SwiftUI,
|
|
269
|
+
In SwiftUI, read `@Environment(\.webAuthenticationSession)` and call
|
|
343
270
|
`authenticate(using:callback:preferredBrowserSession:additionalHeaderFields:)`
|
|
344
|
-
with `.customScheme(
|
|
345
|
-
only when the provider flow should
|
|
271
|
+
with `.customScheme(...)` or `.https(host:path:)`. Ask for the `.ephemeral`
|
|
272
|
+
browser session only when the provider's flow should not share cookies.
|
|
346
273
|
|
|
347
|
-
## Password AutoFill
|
|
274
|
+
## Password AutoFill
|
|
348
275
|
|
|
349
|
-
|
|
350
|
-
|
|
276
|
+
`ASAuthorizationPasswordProvider` puts saved Keychain passwords in the same
|
|
277
|
+
controller as Sign in with Apple. The delegate then branches on the credential:
|
|
351
278
|
|
|
352
279
|
```swift
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
authorizationRequests: [appleIDRequest, passwordRequest]
|
|
361
|
-
)
|
|
362
|
-
controller.delegate = self
|
|
363
|
-
controller.presentationContextProvider = self
|
|
364
|
-
controller.performRequests()
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// In delegate:
|
|
368
|
-
func authorizationController(
|
|
369
|
-
controller: ASAuthorizationController,
|
|
370
|
-
didCompleteWithAuthorization authorization: ASAuthorization
|
|
371
|
-
) {
|
|
372
|
-
switch authorization.credential {
|
|
373
|
-
case let appleIDCredential as ASAuthorizationAppleIDCredential:
|
|
374
|
-
handleAppleIDLogin(appleIDCredential)
|
|
375
|
-
case let passwordCredential as ASPasswordCredential:
|
|
376
|
-
// User selected a saved password from keychain
|
|
377
|
-
signInWithPassword(
|
|
378
|
-
username: passwordCredential.user,
|
|
379
|
-
password: passwordCredential.password
|
|
380
|
-
)
|
|
381
|
-
default:
|
|
382
|
-
break
|
|
383
|
-
}
|
|
280
|
+
switch authorization.credential {
|
|
281
|
+
case let apple as ASAuthorizationAppleIDCredential:
|
|
282
|
+
finishApple(apple)
|
|
283
|
+
case let saved as ASPasswordCredential:
|
|
284
|
+
signIn(username: saved.user, password: saved.password)
|
|
285
|
+
default:
|
|
286
|
+
break
|
|
384
287
|
}
|
|
385
288
|
```
|
|
386
289
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
```swift
|
|
390
|
-
usernameField.textContentType = .username
|
|
391
|
-
passwordField.textContentType = .password
|
|
392
|
-
```
|
|
290
|
+
AutoFill only lights up when the text fields declare `textContentType`
|
|
291
|
+
`.username` and `.password`.
|
|
393
292
|
|
|
394
|
-
## Biometric
|
|
293
|
+
## Biometric re-authentication
|
|
395
294
|
|
|
396
|
-
Use `LAContext`
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
for `SecAccessControl` and `.biometryCurrentSet` patterns.
|
|
295
|
+
Use `LAContext` to confirm the device owner before opening account settings or
|
|
296
|
+
a sensitive action. The `Bool` it returns is a UI gate, not permission to read
|
|
297
|
+
a secret. Put Keychain access control on anything valuable (see
|
|
298
|
+
[Keychain and biometrics](references/keychain-biometric.md)).
|
|
401
299
|
|
|
402
300
|
```swift
|
|
403
301
|
import LocalAuthentication
|
|
404
302
|
|
|
405
|
-
func
|
|
303
|
+
func confirmOwner() async -> Bool {
|
|
406
304
|
let context = LAContext()
|
|
407
|
-
var
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
.deviceOwnerAuthenticationWithBiometrics, error: &error
|
|
411
|
-
) else {
|
|
412
|
-
throw AuthError.biometricsUnavailable
|
|
305
|
+
var problem: NSError?
|
|
306
|
+
guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &problem) else {
|
|
307
|
+
return false
|
|
413
308
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
localizedReason: "Sign in to your account"
|
|
418
|
-
)
|
|
309
|
+
let reason = "Confirm it's you to change your email"
|
|
310
|
+
let passed = try? await context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason)
|
|
311
|
+
return passed == true
|
|
419
312
|
}
|
|
420
313
|
```
|
|
421
314
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
## Security
|
|
426
|
-
|
|
427
|
-
This skill
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
-
|
|
446
|
-
|
|
447
|
-
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
websites.
|
|
459
|
-
- Expanding authentication work into CryptoKit, Secure Enclave, certificate
|
|
460
|
-
pinning, or OWASP MASVS. Route those to `swift-security`.
|
|
461
|
-
|
|
462
|
-
## Review Checklist
|
|
463
|
-
|
|
464
|
-
- [ ] "Sign in with Apple" capability added in Xcode project
|
|
315
|
+
Info.plist must include `NSFaceIDUsageDescription`; without it the app crashes
|
|
316
|
+
on Face ID hardware.
|
|
317
|
+
|
|
318
|
+
## Security boundaries
|
|
319
|
+
|
|
320
|
+
This skill covers: Apple ID sign-in, passkeys, AutoFill of saved passwords, the
|
|
321
|
+
`ASAuthorizationController` flow, presenting OAuth web sessions, checking
|
|
322
|
+
credential state, and biometric re-authentication on the device.
|
|
323
|
+
|
|
324
|
+
The minimum storage rule stays here too: tokens and secrets live in the
|
|
325
|
+
Keychain, and `LAContext.evaluatePolicy` on its own must never unlock a
|
|
326
|
+
protected secret.
|
|
327
|
+
|
|
328
|
+
Hand off to `swift-security`: Keychain architecture and migration,
|
|
329
|
+
access-control policy design, CryptoKit, Secure Enclave, certificate pinning
|
|
330
|
+
and trust evaluation, sharing items between apps, hardening local storage, and
|
|
331
|
+
mapping against OWASP MASVS/MASTG. App and device attestation belongs to `device-integrity`.
|
|
332
|
+
|
|
333
|
+
## Common mistakes
|
|
334
|
+
|
|
335
|
+
- Assuming a cached session means Apple has not revoked the link. Check state at launch and react to the revocation notification.
|
|
336
|
+
- Jumping straight to the login form without first asking for saved credentials;
|
|
337
|
+
`.notInteractive` is simply the "nothing saved" answer.
|
|
338
|
+
- Force-unwrapping `email` or `fullName`.
|
|
339
|
+
- Building an `ASAuthorizationController` with no presentation context provider.
|
|
340
|
+
- Writing JWTs from Apple, one-time codes, session tokens, passwords or
|
|
341
|
+
passkey server records into `UserDefaults`, plain files or Core Data. Use the
|
|
342
|
+
Keychain, and keep passkey verification on the server.
|
|
343
|
+
- Passkey requests without a `webcredentials:` Associated Domains entry, or
|
|
344
|
+
using app passkeys for an unrelated website.
|
|
345
|
+
- Drifting into CryptoKit, Secure Enclave, pinning or MASVS work instead of
|
|
346
|
+
routing it to `swift-security`.
|
|
347
|
+
|
|
348
|
+
## Review checklist
|
|
349
|
+
|
|
350
|
+
- [ ] Sign in with Apple capability enabled
|
|
465
351
|
- [ ] `ASAuthorizationControllerPresentationContextProviding` implemented
|
|
466
|
-
- [ ]
|
|
467
|
-
- [ ] `credentialRevokedNotification`
|
|
468
|
-
- [ ] `email` and `fullName`
|
|
469
|
-
- [ ] `identityToken`
|
|
470
|
-
- [ ] Tokens
|
|
471
|
-
- [ ]
|
|
472
|
-
- [ ]
|
|
473
|
-
- [ ] `NSFaceIDUsageDescription`
|
|
474
|
-
- [ ] `ASWebAuthenticationSession
|
|
475
|
-
- [ ] `prefersEphemeralWebBrowserSession` set
|
|
476
|
-
- [ ] `textContentType` set on username
|
|
477
|
-
- [ ] Passkey relying party
|
|
478
|
-
- [ ] Passkey
|
|
479
|
-
- [ ] Deep Keychain, CryptoKit, Secure Enclave,
|
|
352
|
+
- [ ] `getCredentialState(forUserID:completion:)` runs on every launch
|
|
353
|
+
- [ ] `credentialRevokedNotification` observed and signs the user out
|
|
354
|
+
- [ ] `email` and `fullName` saved on first authorization
|
|
355
|
+
- [ ] `identityToken` validated by the server
|
|
356
|
+
- [ ] Tokens kept in the Keychain
|
|
357
|
+
- [ ] Existing-account flows run before the login UI
|
|
358
|
+
- [ ] `.canceled`, `.failed` and `.notInteractive` handled
|
|
359
|
+
- [ ] `NSFaceIDUsageDescription` present
|
|
360
|
+
- [ ] OAuth uses `ASWebAuthenticationSession`, not `WKWebView`
|
|
361
|
+
- [ ] `prefersEphemeralWebBrowserSession` set where cookie isolation matters
|
|
362
|
+
- [ ] `textContentType` set on username and password fields
|
|
363
|
+
- [ ] Passkey relying party listed under `webcredentials:` Associated Domains
|
|
364
|
+
- [ ] Passkey challenges issued and verified by the server
|
|
365
|
+
- [ ] Deep Keychain, CryptoKit, Secure Enclave, pinning and MASVS questions routed to `swift-security`
|
|
480
366
|
|
|
481
367
|
## References
|
|
482
368
|
|
|
483
|
-
-
|
|
484
|
-
-
|
|
485
|
-
- [
|
|
486
|
-
- [
|
|
487
|
-
- [
|
|
488
|
-
- [
|
|
489
|
-
- [
|
|
490
|
-
- [
|
|
491
|
-
- [
|
|
492
|
-
-
|
|
493
|
-
- [SignInWithAppleButton](https://sosumi.ai/documentation/authenticationservices/signinwithapplebutton)
|
|
494
|
-
- [Implementing User Authentication with Sign in with Apple](https://sosumi.ai/documentation/authenticationservices/implementing-user-authentication-with-sign-in-with-apple)
|
|
369
|
+
- [AuthenticationServices](https://developer.apple.com/documentation/authenticationservices)
|
|
370
|
+
- [ASAuthorizationAppleIDProvider](https://developer.apple.com/documentation/authenticationservices/asauthorizationappleidprovider)
|
|
371
|
+
- [ASAuthorizationAppleIDCredential](https://developer.apple.com/documentation/authenticationservices/asauthorizationappleidcredential)
|
|
372
|
+
- [ASAuthorizationController](https://developer.apple.com/documentation/authenticationservices/asauthorizationcontroller)
|
|
373
|
+
- [ASWebAuthenticationSession](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession)
|
|
374
|
+
- [Supporting passkeys](https://developer.apple.com/documentation/authenticationservices/supporting-passkeys)
|
|
375
|
+
- [ASAuthorizationPlatformPublicKeyCredentialProvider](https://developer.apple.com/documentation/authenticationservices/asauthorizationplatformpublickeycredentialprovider)
|
|
376
|
+
- [ASAuthorizationPasswordProvider](https://developer.apple.com/documentation/authenticationservices/asauthorizationpasswordprovider)
|
|
377
|
+
- [SignInWithAppleButton](https://developer.apple.com/documentation/authenticationservices/signinwithapplebutton)
|
|
378
|
+
- Sample code: the AuthenticationServices sample on implementing user authentication with Apple ID sign-in
|