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,