@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.1
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 +21 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +285 -285
- package/package.json +3 -3
- package/pipeline/lib/redact.mjs +3 -2
- package/pipeline/scripts/_notices.mjs +1 -1
- package/pipeline/scripts/gen-skills-index.mjs +13 -1
- package/pipeline/scripts/pre-commit-check.sh +4 -0
- package/pipeline/skills/.skill-manifest.json +69 -69
- 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 +345 -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 +107 -121
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +145 -165
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +306 -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 +335 -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 +277 -381
- package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +135 -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 +274 -384
- package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +173 -321
- 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 +228 -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 +302 -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 +236 -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/localization-reuse-map/SKILL.md +3 -3
- package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +1 -1
- package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +8 -7
- package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +2 -2
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +5 -2
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/_shared.py +100 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +45 -26
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +14 -16
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +12 -5
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +2 -1
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +6 -5
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +44 -18
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +5 -2
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +10 -11
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +4 -33
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +12 -59
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +297 -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/skill-creator/template.md +7 -1
- 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 +302 -241
- 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 +304 -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 +183 -162
- 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 +411 -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 +375 -491
- 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 +397 -457
- package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +191 -175
- package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +81 -84
- package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +394 -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 +201 -168
- package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +134 -133
- package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +111 -138
- 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 +160 -315
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +224 -288
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +416 -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) sign-up and login via ASAuthorizationPlatformPublicKeyCredentialProvider, ASAuthorizationController credential state and revocation, ASWebAuthenticationSession OAuth, third-party login, Password AutoFill, server identity-token validation, LAContext biometric re-auth prompts and policy. 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,352 @@ 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 is UIWindow; UIWindow() 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
|
-
|
|
107
|
+
@MainActor
|
|
108
|
+
func verifyAppleCredential(for userID: String) async {
|
|
148
109
|
let provider = ASAuthorizationAppleIDProvider()
|
|
149
|
-
|
|
150
|
-
|
|
110
|
+
let credentialState: ASAuthorizationAppleIDProvider.CredentialState
|
|
111
|
+
do {
|
|
112
|
+
credentialState = try await provider.credentialState(forUserID: userID)
|
|
113
|
+
} catch {
|
|
114
|
+
SessionStore.shared.presentLogin()
|
|
151
115
|
return
|
|
152
116
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
case .revoked:
|
|
160
|
-
// User revoked -- sign out and clear local data
|
|
161
|
-
signOut()
|
|
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()
|
|
170
|
-
}
|
|
171
|
-
}
|
|
117
|
+
switch credentialState {
|
|
118
|
+
case .authorized: SessionStore.shared.resume()
|
|
119
|
+
case .revoked: SessionStore.shared.signOutAndWipe()
|
|
120
|
+
case .notFound: SessionStore.shared.presentLogin()
|
|
121
|
+
case .transferred: SessionStore.shared.migrateTransferredUser(userID)
|
|
122
|
+
@unknown default: SessionStore.shared.presentLogin()
|
|
172
123
|
}
|
|
173
124
|
}
|
|
174
125
|
```
|
|
175
126
|
|
|
176
|
-
|
|
127
|
+
The function runs on the main actor and awaits the async form of
|
|
128
|
+
`getCredentialState(forUserID:completion:)`, so the session calls need no hop
|
|
129
|
+
back to the main queue.
|
|
177
130
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
queue: .main
|
|
183
|
-
) { _ in
|
|
184
|
-
// Sign out immediately
|
|
185
|
-
AuthManager.shared.signOut()
|
|
186
|
-
}
|
|
187
|
-
```
|
|
131
|
+
`.transferred` means the app moved to a different developer team and the user
|
|
132
|
+
identifier must be migrated. Also subscribe to
|
|
133
|
+
`ASAuthorizationAppleIDProvider.credentialRevokedNotification` and sign the
|
|
134
|
+
user out as soon as it fires.
|
|
188
135
|
|
|
189
|
-
|
|
136
|
+
### Server validation
|
|
190
137
|
|
|
191
|
-
|
|
192
|
-
|
|
138
|
+
Never accept the identity token on the device alone. Hand both values to your
|
|
139
|
+
backend and keep only what it returns:
|
|
193
140
|
|
|
194
141
|
```swift
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
142
|
+
enum SessionAPI {
|
|
143
|
+
struct Session: Decodable { let accessToken: String }
|
|
144
|
+
private static let transport = URLSession(configuration: .ephemeral)
|
|
145
|
+
|
|
146
|
+
static func exchange(token: Data?, code: Data?) async throws {
|
|
147
|
+
guard let token, let code,
|
|
148
|
+
let jwt = String(data: token, encoding: .utf8),
|
|
149
|
+
let grant = String(data: code, encoding: .utf8) else { throw URLError(.badServerResponse) }
|
|
150
|
+
|
|
151
|
+
guard let endpoint = URL(string: "https://api.tasknest.app/auth/apple") else { throw URLError(.badURL) }
|
|
152
|
+
var call = URLRequest(url: endpoint)
|
|
153
|
+
call.httpMethod = "POST"
|
|
154
|
+
call.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
155
|
+
let body = try JSONEncoder().encode(["identity_token": jwt, "authorization_code": grant])
|
|
156
|
+
let (bytes, reply) = try await transport.upload(for: call, from: body)
|
|
157
|
+
let status = (reply as? HTTPURLResponse)?.statusCode ?? 0
|
|
158
|
+
if status != 200 { throw URLError(.userAuthenticationRequired) }
|
|
159
|
+
|
|
160
|
+
let session = try JSONDecoder().decode(Session.self, from: bytes)
|
|
161
|
+
try TokenVault(service: "auth").store(Data(session.accessToken.utf8), account: "access")
|
|
213
162
|
}
|
|
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
163
|
}
|
|
218
164
|
```
|
|
219
165
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
`
|
|
166
|
+
`TokenVault` is in the [Keychain reference](references/keychain-biometric.md). The
|
|
167
|
+
server verifies the JWT signature against Apple's published key set (the JWKS
|
|
168
|
+
document served from `appleid.apple.com` under `/auth/keys`) and checks that `iss` is
|
|
169
|
+
`https://appleid.apple.com`, `aud` is your bundle ID, and `exp` is in the future.
|
|
223
170
|
|
|
224
|
-
|
|
171
|
+
### Existing accounts first
|
|
225
172
|
|
|
226
|
-
|
|
227
|
-
|
|
173
|
+
Before showing a login screen, quietly ask the system whether the person
|
|
174
|
+
already has an Apple ID credential or a saved password for the app:
|
|
228
175
|
|
|
229
176
|
```swift
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
)
|
|
177
|
+
extension WelcomeViewController {
|
|
178
|
+
func offerSavedCredentials() {
|
|
179
|
+
let requests: [ASAuthorizationRequest] = [
|
|
180
|
+
ASAuthorizationAppleIDProvider().createRequest(),
|
|
181
|
+
ASAuthorizationPasswordProvider().createRequest(),
|
|
182
|
+
]
|
|
183
|
+
let controller = ASAuthorizationController(authorizationRequests: requests)
|
|
184
|
+
controller.delegate = self
|
|
185
|
+
controller.presentationContextProvider = self
|
|
186
|
+
controller.performRequests(options: .preferImmediatelyAvailableCredentials)
|
|
187
|
+
}
|
|
242
188
|
}
|
|
243
189
|
```
|
|
244
190
|
|
|
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.
|
|
255
|
-
|
|
256
|
-
For platform passkeys synced through iCloud Keychain, request a server-provided
|
|
257
|
-
challenge and create requests with `ASAuthorizationPlatformPublicKeyCredentialProvider`:
|
|
258
|
-
|
|
259
|
-
```swift
|
|
260
|
-
let challenge: Data = try await server.registrationChallenge()
|
|
261
|
-
let userID: Data = try await server.passkeyUserID()
|
|
262
|
-
let provider = ASAuthorizationPlatformPublicKeyCredentialProvider(
|
|
263
|
-
relyingPartyIdentifier: "example.com"
|
|
264
|
-
)
|
|
265
|
-
let request = provider.createCredentialRegistrationRequest(
|
|
266
|
-
challenge: challenge,
|
|
267
|
-
name: username,
|
|
268
|
-
userID: userID
|
|
269
|
-
)
|
|
270
|
-
|
|
271
|
-
let controller = ASAuthorizationController(authorizationRequests: [request])
|
|
272
|
-
controller.delegate = self
|
|
273
|
-
controller.presentationContextProvider = self
|
|
274
|
-
controller.performRequests()
|
|
275
|
-
```
|
|
191
|
+
Run it from `viewDidAppear` or during launch. When nothing is stored, the
|
|
192
|
+
delegate receives `.notInteractive`; treat that silently and show the usual
|
|
193
|
+
login.
|
|
276
194
|
|
|
277
|
-
|
|
278
|
-
server challenge, then send the resulting registration or assertion object to
|
|
279
|
-
the relying-party server for verification:
|
|
195
|
+
### SwiftUI button
|
|
280
196
|
|
|
281
197
|
```swift
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
198
|
+
import AuthenticationServices
|
|
199
|
+
import SwiftUI
|
|
200
|
+
|
|
201
|
+
struct AppleSignInRow: View {
|
|
202
|
+
let model: LoginModel
|
|
203
|
+
|
|
204
|
+
var body: some View {
|
|
205
|
+
SignInWithAppleButton(.continue) { request in
|
|
206
|
+
request.requestedScopes = [.fullName, .email]
|
|
207
|
+
} onCompletion: { outcome in
|
|
208
|
+
switch outcome {
|
|
209
|
+
case .success(let auth):
|
|
210
|
+
if let apple = auth.credential as? ASAuthorizationAppleIDCredential {
|
|
211
|
+
model.completeAppleSignIn(apple) // same server validation as UIKit
|
|
212
|
+
}
|
|
213
|
+
case .failure(let error):
|
|
214
|
+
model.report(error)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
.signInWithAppleButtonStyle(.whiteOutline)
|
|
218
|
+
.frame(height: 48)
|
|
219
|
+
}
|
|
291
220
|
}
|
|
292
221
|
```
|
|
293
222
|
|
|
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.
|
|
223
|
+
The button lives in AuthenticationServices but needs SwiftUI imported in the
|
|
224
|
+
same file.
|
|
300
225
|
|
|
301
|
-
##
|
|
226
|
+
## Passkeys
|
|
302
227
|
|
|
303
|
-
|
|
304
|
-
|
|
228
|
+
Passkeys replace passwords with WebAuthn credentials for both account creation and login. They need
|
|
229
|
+
the Associated Domains entitlement with a `webcredentials:` entry for the
|
|
230
|
+
relying-party domain; requests for a domain that is not associated fail.
|
|
231
|
+
Platform passkeys sync through iCloud Keychain.
|
|
305
232
|
|
|
306
|
-
|
|
307
|
-
|
|
233
|
+
- Create `ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier:)`.
|
|
234
|
+
- Register with `createCredentialRegistrationRequest(challenge:name:userID:)` using a
|
|
235
|
+
challenge and user ID `Data` from the server.
|
|
236
|
+
- Sign in with `createCredentialAssertionRequest(challenge:)` and a fresh server challenge.
|
|
237
|
+
- Results arrive as `ASAuthorizationPlatformPublicKeyCredentialRegistration` or
|
|
238
|
+
`ASAuthorizationPlatformPublicKeyCredentialAssertion`; the relying-party server verifies them.
|
|
239
|
+
- For inline suggestions, mark the username field `.username`, include an
|
|
240
|
+
assertion request, and call `performAutoFillAssistedRequests()`.
|
|
241
|
+
- Reach for `ASAuthorizationSecurityKeyPublicKeyCredentialProvider` only when a
|
|
242
|
+
physical security key is required.
|
|
308
243
|
|
|
309
|
-
|
|
310
|
-
private weak var presentationAnchor: ASPresentationAnchor?
|
|
244
|
+
Details and code: [passkeys reference](references/passkeys.md).
|
|
311
245
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
246
|
+
## OAuth with `ASWebAuthenticationSession`
|
|
247
|
+
|
|
248
|
+
Third-party logins (Google, GitHub and similar) go through
|
|
249
|
+
`ASWebAuthenticationSession`, never an embedded `WKWebView`.
|
|
315
250
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
guard
|
|
324
|
-
let
|
|
325
|
-
|
|
326
|
-
Task { await
|
|
251
|
+
```swift
|
|
252
|
+
final class GitHubLogin: NSObject, ASWebAuthenticationPresentationContextProviding {
|
|
253
|
+
private var session: ASWebAuthenticationSession?
|
|
254
|
+
|
|
255
|
+
func start(authorizeURL: URL) {
|
|
256
|
+
let session = ASWebAuthenticationSession(url: authorizeURL,
|
|
257
|
+
callback: .customScheme("tasknest")) { url, error in
|
|
258
|
+
guard error == nil, let url,
|
|
259
|
+
let items = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems,
|
|
260
|
+
let code = items.first(where: { $0.name == "code" })?.value else { return }
|
|
261
|
+
Task { try await OAuthBackend.redeem(code: code) }
|
|
327
262
|
}
|
|
328
263
|
session.presentationContextProvider = self
|
|
329
|
-
session.prefersEphemeralWebBrowserSession = true
|
|
264
|
+
session.prefersEphemeralWebBrowserSession = true // no shared browser cookies
|
|
330
265
|
session.start()
|
|
266
|
+
self.session = session
|
|
331
267
|
}
|
|
332
268
|
|
|
333
|
-
func presentationAnchor(for
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
return
|
|
269
|
+
func presentationAnchor(for webSession: ASWebAuthenticationSession) -> ASPresentationAnchor {
|
|
270
|
+
let scenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }
|
|
271
|
+
if let key = scenes.compactMap(\.keyWindow).first { return key }
|
|
272
|
+
guard let scene = scenes.first else { preconditionFailure("No window scene to present from") }
|
|
273
|
+
return ASPresentationAnchor(windowScene: scene)
|
|
338
274
|
}
|
|
339
275
|
}
|
|
340
276
|
```
|
|
341
277
|
|
|
342
|
-
In SwiftUI,
|
|
278
|
+
In SwiftUI, read `@Environment(\.webAuthenticationSession)` and call
|
|
343
279
|
`authenticate(using:callback:preferredBrowserSession:additionalHeaderFields:)`
|
|
344
|
-
with `.customScheme(
|
|
345
|
-
only when the provider flow should
|
|
280
|
+
with `.customScheme(...)` or `.https(host:path:)`. Ask for the `.ephemeral`
|
|
281
|
+
browser session only when the provider's flow should not share cookies.
|
|
346
282
|
|
|
347
|
-
## Password AutoFill
|
|
283
|
+
## Password AutoFill
|
|
348
284
|
|
|
349
|
-
|
|
350
|
-
|
|
285
|
+
`ASAuthorizationPasswordProvider` puts saved Keychain passwords in the same
|
|
286
|
+
controller as Sign in with Apple. The delegate then branches on the credential:
|
|
351
287
|
|
|
352
288
|
```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
|
-
}
|
|
289
|
+
switch authorization.credential {
|
|
290
|
+
case let apple as ASAuthorizationAppleIDCredential:
|
|
291
|
+
finishApple(apple)
|
|
292
|
+
case let saved as ASPasswordCredential:
|
|
293
|
+
signIn(username: saved.user, password: saved.password)
|
|
294
|
+
default:
|
|
295
|
+
break
|
|
384
296
|
}
|
|
385
297
|
```
|
|
386
298
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
```swift
|
|
390
|
-
usernameField.textContentType = .username
|
|
391
|
-
passwordField.textContentType = .password
|
|
392
|
-
```
|
|
299
|
+
AutoFill only lights up when the text fields declare `textContentType`
|
|
300
|
+
`.username` and `.password`.
|
|
393
301
|
|
|
394
|
-
## Biometric
|
|
302
|
+
## Biometric re-authentication
|
|
395
303
|
|
|
396
|
-
Use `LAContext`
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
for `SecAccessControl` and `.biometryCurrentSet` patterns.
|
|
304
|
+
Use `LAContext` to confirm the device owner before opening account settings or
|
|
305
|
+
a sensitive action. The `Bool` it returns is a UI gate, not permission to read
|
|
306
|
+
a secret. Put Keychain access control on anything valuable (see
|
|
307
|
+
[Keychain and biometrics](references/keychain-biometric.md)).
|
|
401
308
|
|
|
402
309
|
```swift
|
|
403
310
|
import LocalAuthentication
|
|
404
311
|
|
|
405
|
-
func
|
|
312
|
+
func confirmOwner() async -> Bool {
|
|
406
313
|
let context = LAContext()
|
|
407
|
-
var
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
.deviceOwnerAuthenticationWithBiometrics, error: &error
|
|
411
|
-
) else {
|
|
412
|
-
throw AuthError.biometricsUnavailable
|
|
314
|
+
var problem: NSError?
|
|
315
|
+
guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &problem) else {
|
|
316
|
+
return false
|
|
413
317
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
localizedReason: "Sign in to your account"
|
|
418
|
-
)
|
|
318
|
+
let reason = "Confirm it's you to change your email"
|
|
319
|
+
let passed = try? await context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason)
|
|
320
|
+
return passed == true
|
|
419
321
|
}
|
|
420
322
|
```
|
|
421
323
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
## Common
|
|
444
|
-
|
|
445
|
-
- Assuming a
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
## Review Checklist
|
|
463
|
-
|
|
464
|
-
- [ ] "Sign in with Apple" capability added in Xcode project
|
|
324
|
+
Info.plist must include `NSFaceIDUsageDescription`, the same rule the
|
|
325
|
+
`swift-security` skill sets for biometric keychain items. The `LAContext` header
|
|
326
|
+
asks apps to supply it: its string is shown the first time the app uses Face ID,
|
|
327
|
+
and once the user denies Face ID for the app, evaluations fail with
|
|
328
|
+
`LAError.biometryNotAvailable`.
|
|
329
|
+
|
|
330
|
+
## Security boundaries
|
|
331
|
+
|
|
332
|
+
This skill covers: Apple ID sign-in, passkeys, AutoFill of saved passwords, the
|
|
333
|
+
`ASAuthorizationController` flow, presenting OAuth web sessions, checking
|
|
334
|
+
credential state, and biometric re-authentication on the device.
|
|
335
|
+
|
|
336
|
+
The minimum storage rule stays here too: tokens and secrets live in the
|
|
337
|
+
Keychain, and `LAContext.evaluatePolicy` on its own must never unlock a
|
|
338
|
+
protected secret.
|
|
339
|
+
|
|
340
|
+
Hand off to `swift-security`: Keychain architecture and migration,
|
|
341
|
+
biometric-gated keychain items and their access-control policy, CryptoKit, Secure Enclave, certificate pinning
|
|
342
|
+
and trust evaluation, sharing items between apps, hardening local storage, and
|
|
343
|
+
mapping against OWASP MASVS/MASTG. App and device attestation belongs to `device-integrity`.
|
|
344
|
+
|
|
345
|
+
## Common mistakes
|
|
346
|
+
|
|
347
|
+
- Assuming a cached session means Apple has not revoked the link. Check state at launch and react to the revocation notification.
|
|
348
|
+
- Jumping straight to the login form without first asking for saved credentials;
|
|
349
|
+
`.notInteractive` is simply the "nothing saved" answer.
|
|
350
|
+
- Force-unwrapping `email` or `fullName`.
|
|
351
|
+
- Building an `ASAuthorizationController` with no presentation context provider.
|
|
352
|
+
- Writing JWTs from Apple, one-time codes, session tokens, passwords or
|
|
353
|
+
passkey server records into `UserDefaults`, plain files or Core Data. Use the
|
|
354
|
+
Keychain, and keep passkey verification on the server.
|
|
355
|
+
- Passkey requests without a `webcredentials:` Associated Domains entry, or
|
|
356
|
+
using app passkeys for an unrelated website.
|
|
357
|
+
- Drifting into CryptoKit, Secure Enclave, pinning or MASVS work instead of
|
|
358
|
+
routing it to `swift-security`.
|
|
359
|
+
|
|
360
|
+
## Review checklist
|
|
361
|
+
|
|
362
|
+
- [ ] Sign in with Apple capability enabled
|
|
465
363
|
- [ ] `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,
|
|
364
|
+
- [ ] `getCredentialState(forUserID:completion:)` runs on every launch
|
|
365
|
+
- [ ] `credentialRevokedNotification` observed and signs the user out
|
|
366
|
+
- [ ] `email` and `fullName` saved on first authorization
|
|
367
|
+
- [ ] `identityToken` validated by the server
|
|
368
|
+
- [ ] Tokens kept in the Keychain
|
|
369
|
+
- [ ] Existing-account flows run before the login UI
|
|
370
|
+
- [ ] `.canceled`, `.failed` and `.notInteractive` handled
|
|
371
|
+
- [ ] `NSFaceIDUsageDescription` present
|
|
372
|
+
- [ ] OAuth uses `ASWebAuthenticationSession`, not `WKWebView`
|
|
373
|
+
- [ ] `prefersEphemeralWebBrowserSession` set where cookie isolation matters
|
|
374
|
+
- [ ] `textContentType` set on username and password fields
|
|
375
|
+
- [ ] Passkey relying party listed under `webcredentials:` Associated Domains
|
|
376
|
+
- [ ] Passkey challenges issued and verified by the server
|
|
377
|
+
- [ ] Deep Keychain, CryptoKit, Secure Enclave, pinning and MASVS questions routed to `swift-security`
|
|
480
378
|
|
|
481
379
|
## References
|
|
482
380
|
|
|
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)
|
|
381
|
+
- [AuthenticationServices](https://developer.apple.com/documentation/authenticationservices)
|
|
382
|
+
- [ASAuthorizationAppleIDProvider](https://developer.apple.com/documentation/authenticationservices/asauthorizationappleidprovider)
|
|
383
|
+
- [ASAuthorizationAppleIDCredential](https://developer.apple.com/documentation/authenticationservices/asauthorizationappleidcredential)
|
|
384
|
+
- [ASAuthorizationController](https://developer.apple.com/documentation/authenticationservices/asauthorizationcontroller)
|
|
385
|
+
- [ASWebAuthenticationSession](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession)
|
|
386
|
+
- [Supporting passkeys](https://developer.apple.com/documentation/authenticationservices/supporting-passkeys)
|
|
387
|
+
- [ASAuthorizationPlatformPublicKeyCredentialProvider](https://developer.apple.com/documentation/authenticationservices/asauthorizationplatformpublickeycredentialprovider)
|
|
388
|
+
- [ASAuthorizationPasswordProvider](https://developer.apple.com/documentation/authenticationservices/asauthorizationpasswordprovider)
|
|
389
|
+
- [SignInWithAppleButton](https://developer.apple.com/documentation/authenticationservices/signinwithapplebutton)
|
|
390
|
+
- Sample code: the AuthenticationServices sample on implementing user authentication with Apple ID sign-in
|