Passkey Provider Assertion Help

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.

Accepted Reply

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 that ASPasskeyCredentialIdentity.user and ASPasskeyCredentialIdentity.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.
  • Thank you so much for the reply!

    It does look like the test sites actually are not even checking for the userHandle at all as I can pass in a garbage string and it will still work (All the other fields will specifically fail if I mess them up). So this looks like the issue for sure.

    What I'm still confused on though, is how do I get that "user Id"? In the passkeyRegistration the only information I have access to is the ASPasskeyCredentialRequest.credentialIdentity.user

  • you need to cast the credentialIdentity to ASPasskeyCredentialIdentity then you'll see the userHandle. something like that: let credIdentity: ASPasskeyCredentialIdentity = request.credentialIdentity as! ASPasskeyCredentialIdentity let userHandle: Data = credIdentity.userHandle

  • This is very helpful! It's tricky to find. I have a related question. How do we find other info such as attestationPreference through this interface? Is there a way to ASAuthorizationPublicKeyCredentialRegistrationRequest? I don't seem to see how we can achieve this through ASPasskeyCredentialRequest in prepareInterface(forPasskeyRegistration:) for iOS. A more broader question is: do we have access to the WebAuthn extensions in third-party passkey manager? Thanks!

Add a Comment

Replies

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 that ASPasskeyCredentialIdentity.user and ASPasskeyCredentialIdentity.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.
  • Thank you so much for the reply!

    It does look like the test sites actually are not even checking for the userHandle at all as I can pass in a garbage string and it will still work (All the other fields will specifically fail if I mess them up). So this looks like the issue for sure.

    What I'm still confused on though, is how do I get that "user Id"? In the passkeyRegistration the only information I have access to is the ASPasskeyCredentialRequest.credentialIdentity.user

  • you need to cast the credentialIdentity to ASPasskeyCredentialIdentity then you'll see the userHandle. something like that: let credIdentity: ASPasskeyCredentialIdentity = request.credentialIdentity as! ASPasskeyCredentialIdentity let userHandle: Data = credIdentity.userHandle

  • This is very helpful! It's tricky to find. I have a related question. How do we find other info such as attestationPreference through this interface? Is there a way to ASAuthorizationPublicKeyCredentialRegistrationRequest? I don't seem to see how we can achieve this through ASPasskeyCredentialRequest in prepareInterface(forPasskeyRegistration:) for iOS. A more broader question is: do we have access to the WebAuthn extensions in third-party passkey manager? Thanks!

Add a Comment