Skip to content

docs: correct iOS device-authentication guide#2691

Open
hperl wants to merge 2 commits into
masterfrom
henning/deviceauthn-ios-doc-fixes
Open

docs: correct iOS device-authentication guide#2691
hperl wants to merge 2 commits into
masterfrom
henning/deviceauthn-ios-doc-fixes

Conversation

@hperl

@hperl hperl commented Jul 23, 2026

Copy link
Copy Markdown
Member

Corrects three factual errors in the iOS device-authentication guide, found by comparing it against a reference iOS client implementation (ory-corp/cloud PR #12950).

  • iOS Simulator is usable. The guide said the emulator cannot be used at all. In fact you can run device auth on the Simulator during development by enabling relaxed attestation, which accepts the software-backed attestation produced by the Ory Swift SDK's OryClientSimulator product. Relaxed attestation is refused once the project leaves the development environment.
  • App Attest keys cannot be biometric-gated through access control. The guide said to create the signing key with .biometryCurrentSet in its access control. App Attest keys are system-managed and accept no access-control flags, so the client must run a LocalAuthentication check before every generateAssertion call. The server trusts the platform declaration on iOS because it cannot verify biometric gating remotely, which makes the client-side gate security-critical.
  • Keychain items survive app deletion. The guide implied kSecAttrAccessibleWhenUnlockedThisDeviceOnly causes uninstall to purge the stored blob. That attribute keeps the blob off encrypted backups and device migrations, not off a reinstall. A fresh install must detect the reinstall (for example via a UserDefaults install marker, which the system does clear) and drop any stale artifacts before use.

- The iOS Simulator can run device auth via relaxed attestation and the
  OryClientSimulator product, rather than being unusable outright.
- App Attest keys cannot be biometric-gated through access control; the
  client gates signing with a LocalAuthentication check the server trusts.
- Keychain items survive app deletion; kSecAttrAccessibleWhenUnlockedThisDeviceOnly
  protects backups and device migration, and a fresh install must clear stale keys.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@hperl
hperl requested a review from vinckr as a code owner July 23, 2026 09:10
Copilot AI review requested due to automatic review settings July 23, 2026 09:10
@hperl
hperl requested review from aeneasr and unatasha8 as code owners July 23, 2026 09:10
@hperl hperl self-assigned this Jul 23, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR corrects factual and security-relevant guidance in the Kratos passwordless “deviceauthn” iOS documentation, aligning the guide with how iOS App Attest / Keychain behavior actually works and how the client must implement biometric gating.

Changes:

  • Clarifies Simulator support by documenting relaxed attestation for development/testing (software-backed attestations via OryClientSimulator).
  • Fixes Keychain guidance to reflect that kSecAttrAccessibleWhenUnlockedThisDeviceOnly does not imply data is purged on uninstall, and recommends reinstall detection + cleanup.
  • Corrects biometric (platform) guidance: App Attest keys can’t be access-control/biometry gated, so the client must gate each assertion with LocalAuthentication and handle biometric-set changes.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/components/Shared/kratos/passwordless/deviceauthn/ios.mdx Updates iOS-specific deviceauthn guidance: Simulator/relaxed attestation notes, Keychain persistence semantics, and biometric gating requirements.
src/components/Shared/kratos/passwordless/deviceauthn/index.mdx Updates shared deviceauthn requirements to reflect Keychain persistence across uninstall and recommend reinstall detection/cleanup.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/components/Shared/kratos/passwordless/deviceauthn/ios.mdx Outdated
Comment thread src/components/Shared/kratos/passwordless/deviceauthn/ios.mdx
Comment thread src/components/Shared/kratos/passwordless/deviceauthn/ios.mdx

@pcaillaudm pcaillaudm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, just one non-blocking question

to set on the key; instead, gate signing with a `LocalAuthentication` check
(`LAContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics)`) that must succeed before every `generateAssertion` call,
at both enrollment and login. To approximate `.biometryCurrentSet` semantics — invalidating the key when the enrolled biometric
set changes — pin the context's `evaluatedPolicyDomainState` at enrollment and reject a login when it no longer matches.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed evaluatedPolicyDomainState is marked deprecated in the Apple docs - still fine to recommend here?

This means that the emulator cannot be used.
This means the iOS Simulator cannot produce real attestations. For development and testing you can still run on the Simulator by
enabling <SameDeploymentLink to="kratos/passwordless/deviceauthn#relaxed-attestation-for-testing">relaxed
attestation</SameDeploymentLink>, which accepts software-backed attestations. The Ory Swift SDK ships an `OryClientSimulator`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
attestation</SameDeploymentLink>, which accepts software-backed attestations. The Ory Swift SDK ships an `OryClientSimulator`
attestation</SameDeploymentLink>, which accepts software-backed attestations. The Ory client SDK for Swift ships an `OryClientSimulator`

/// kSecAttrAccessibleWhenUnlockedThisDeviceOnly — never in UserDefaults. The
/// ThisDeviceOnly attribute keeps the blob off encrypted backups and device
/// migrations, so it cannot follow the sealed secret onto another device.
/// Keychain items survive app deletion, so detect a reinstall (for example with

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a little hard to decipher whether 'so detect a reinstall' is a consequence or a direct action for the developer to do. Here is a rewrite of the complete comment. (I didn't do it inline as it's too hard to review the changes.)

/// Store this entire struct inside the Keychain using the attribute
/// 'kSecAttrAccessibleWhenUnlockedThisDeviceOnly'. Never store it in UserDefaults.
/// The 'ThisDeviceOnly' flag prevents this data from being backed up
/// or migrated. The artifacts will never follow a secret to another device.
/// Because the Keychain data survives deletion, you must write code to detect
/// if the current launch is a reinstall. As the system completely wipes
/// UserDefaults data when a user deletes the app (and creates a blank new one on reinstall),
/// you can create a dummy marker/flag in UserDefaults to detect the reinstall. On boot up if
/// a 'PinArtifacts' struct is in the Keychain, but your UserDefaults marker is missing,
/// the user has reinstalled the app. You must delete these stale Keychain artifacts
/// before allowing the app to run. (None of these struct fields are secret on their own).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants