Hi all, I'm trying to create a passkey provider application and I can consistently register a passkey through 'prepareInterface(forPasskeyRegistration registrationRequest: ASCredentialRequest)' everywhere that accepts passkeys.
However, when I attempt to then use that passkey to sign in with 'provideCredentialWithoutUserInteraction(for credentialRequest: ASCredentialRequest)'
My code will work on some sites.. and not others. For instance: https://passkey.org/ and https://webauthn.io/ both work flawlessly.
But if I then try to do the same with Github, Uber, or Coinbase (really any application "in the wild") the assertion portion fails with a generic error like "This passkey can't be used anymore."
I've debugged every request and response object line by line and can't find a single difference between a site that works, and one that doesn't.. Does anyone know why this could be the case?
Here's my assertion code:
guard let request: ASPasskeyCredentialRequest = credentialRequest as? ASPasskeyCredentialRequest else { return }
// Following specs from here: https://www.w3.org/TR/webauthn/#sctn-signature-attestation-types
let hashedRp: [UInt8] = Array(SHA256.hash(data: Data(request.credentialIdentity.serviceIdentifier.identifier.data(using: .utf8) ?? Data([]))))
let flags:[UInt8] = [29] // 00011101 - only one that seems to work
let counter:[UInt8] = [0, 0, 0, 1] // just for testing, always the first for now until this works
let authData = hashedRp + flags + counter
let signature = try privateKey.signature( for: Data(authData + request.clientDataHash) )
let response: ASPasskeyAssertionCredential = ASPasskeyAssertionCredential(
userHandle: Data(request.credentialIdentity.user.utf8),
relyingParty: request.credentialIdentity.serviceIdentifier.identifier,
signature: signature.derRepresentation,
clientDataHash: request.clientDataHash,
authenticatorData: Data(authData),
credentialID: Data(credentialId.utf8)
)
extensionContext.completeAssertionRequest(using: response)
Any help would be very appreciated as I've been stuck on this for a while now.
userHandle: Data(request.credentialIdentity.user.utf8),
It looks like you're passing the user
(the user name) here instead of the userHandle. There are several similarly named fields in WebAuthn which can be confusing:
userHandle
: The "user handle" or "user id" is an opaque blob that serves as a record identifier to the website to identify this specific account. You can think of this like an account number. All passkeys for the same account will have the same userHandle.userName
: This is the user-visible string to identify that specific credential, which will be shown in the system UI. This is generally an email address. Note thatASPasskeyCredentialIdentity.user
andASPasskeyCredentialIdentity.userName
are interchangeable.credentialID
: This is a unique identifier for that specific passkey. Different passkeys for the same account will have different credentialIDs.- WebAuthn also has a "display name" concept, which is supposed to represent an additional user-friendly string to identify the account holder, such as "Firstname Lastname". This is not currently supported on any of our platforms.