From d587ed7a55bcef60287fa0fc7dcdcbcc06e88518 Mon Sep 17 00:00:00 2001 From: Patrick Scheips Date: Tue, 18 Nov 2025 12:46:52 +0100 Subject: [PATCH] Add requireUserPresence option to registration verification Introduces a requireUserPresence parameter to the registration verification flow, allowing user presence checks to be optionally bypassed. This supports scenarios like silent or conditional passkey registration (e.g., iOS 26 conditional registration), where user presence may not be required. --- .../Ceremonies/Registration/AttestationObject.swift | 7 +++++-- .../Ceremonies/Registration/RegistrationCredential.swift | 2 ++ Sources/WebAuthn/WebAuthnManager.swift | 4 ++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift b/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift index bd32cc1..98a98f2 100644 --- a/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift +++ b/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift @@ -25,6 +25,7 @@ public struct AttestationObject: Sendable { func verify( relyingPartyID: String, verificationRequired: Bool, + requireUserPresence: Bool = true, clientDataHash: SHA256.Digest, supportedPublicKeyAlgorithms: [PublicKeyCredentialParameters], pemRootCertificatesByFormat: [AttestationFormat: [Data]] = [:] @@ -35,8 +36,10 @@ public struct AttestationObject: Sendable { throw WebAuthnError.relyingPartyIDHashDoesNotMatch } - guard authenticatorData.flags.userPresent else { - throw WebAuthnError.userPresentFlagNotSet + if requireUserPresence { + guard authenticatorData.flags.userPresent else { + throw WebAuthnError.userPresentFlagNotSet + } } if verificationRequired { diff --git a/Sources/WebAuthn/Ceremonies/Registration/RegistrationCredential.swift b/Sources/WebAuthn/Ceremonies/Registration/RegistrationCredential.swift index bf4fe59..09ea997 100644 --- a/Sources/WebAuthn/Ceremonies/Registration/RegistrationCredential.swift +++ b/Sources/WebAuthn/Ceremonies/Registration/RegistrationCredential.swift @@ -91,6 +91,7 @@ struct ParsedCredentialCreationResponse { func verify( storedChallenge: [UInt8], verifyUser: Bool, + requireUserPresence: Bool, relyingPartyID: String, relyingPartyOrigin: String, supportedPublicKeyAlgorithms: [PublicKeyCredentialParameters], @@ -112,6 +113,7 @@ struct ParsedCredentialCreationResponse { let attestedCredentialData = try await response.attestationObject.verify( relyingPartyID: relyingPartyID, verificationRequired: verifyUser, + requireUserPresence: requireUserPresence, clientDataHash: hash, supportedPublicKeyAlgorithms: supportedPublicKeyAlgorithms, pemRootCertificatesByFormat: pemRootCertificatesByFormat diff --git a/Sources/WebAuthn/WebAuthnManager.swift b/Sources/WebAuthn/WebAuthnManager.swift index feebb20..b78738f 100644 --- a/Sources/WebAuthn/WebAuthnManager.swift +++ b/Sources/WebAuthn/WebAuthnManager.swift @@ -83,6 +83,8 @@ public struct WebAuthnManager: Sendable { /// - challenge: The challenge passed to the authenticator within the preceding registration options. /// - credentialCreationData: The value returned from `navigator.credentials.create()` /// - requireUserVerification: Whether or not to require that the authenticator verified the user. + /// - requireUserPresence: Whether or not to require that the user was present during registration. + /// Set to `false` for silent/conditional passkey registration (e.g., iOS 26 conditional registration). /// - supportedPublicKeyAlgorithms: A list of public key algorithms the Relying Party chooses to restrict /// support to. Defaults to all supported algorithms. /// - pemRootCertificatesByFormat: A list of root certificates used for attestation verification. @@ -95,6 +97,7 @@ public struct WebAuthnManager: Sendable { challenge: [UInt8], credentialCreationData: RegistrationCredential, requireUserVerification: Bool = false, + requireUserPresence: Bool = true, supportedPublicKeyAlgorithms: [PublicKeyCredentialParameters] = .supported, pemRootCertificatesByFormat: [AttestationFormat: [Data]] = [:], confirmCredentialIDNotRegisteredYet: (String) async throws -> Bool @@ -103,6 +106,7 @@ public struct WebAuthnManager: Sendable { let attestedCredentialData = try await parsedData.verify( storedChallenge: challenge, verifyUser: requireUserVerification, + requireUserPresence: requireUserPresence, relyingPartyID: configuration.relyingPartyID, relyingPartyOrigin: configuration.relyingPartyOrigin, supportedPublicKeyAlgorithms: supportedPublicKeyAlgorithms,