aws-samples / amazon-cognito-passwordless-auth

Passwordless authentication with Amazon Cognito: FIDO2 (WebAuthn, support for Passkeys), Magic Link, SMS OTP Step Up
Apache License 2.0
386 stars 70 forks source link

Type Error fido2GetCredential in Safari #188

Open juanf9224 opened 3 months ago

juanf9224 commented 3 months ago

Hi, I've integrated this lib and so far is working great for chrome and Firefox, but we are having issues with safari when getting the credentials because it is throwing a super ambiguous error (attached in the image) from the function fido2GetCredential in fido2.js:214, this is const credential = await navigator.credentials.get({ publicKey, }); I've confirmed that the publicKey object is the same in chrome and safari.

Screenshot 2024-08-22 at 5 11 13 PM
ottokruse commented 3 months ago

Mmmmm doesn't ring a bell yet.

I'm on Safari 17.6 where it works okay, you?

What version of the Passwordless lib are you on?

Bread crumb: const credential = await credentialsContainer?.get({ publicKey, }); that is not the code of this lib. You using a password manager or so that is injecting itself "transparantly"? Say 1Pasword?

juanf9224 commented 3 months ago

Hey @ottokruse thanks for your reply, sorry about the code, I had added some changes when testing, updated the post to reflect actual lib code.

as per the lib version I update it to be on the latest at the time 0.14.0 but same error.

ottokruse commented 3 months ago

Just saw this, try that? Set residentkey (can be set when you call Passwordless.configure on the front end)

https://stackoverflow.com/questions/75350756/getting-type-error-when-using-webauthn-on-ios

ottokruse commented 3 months ago

Which macOS / iOS version are you on ?

juanf9224 commented 3 months ago

MacOS 14.6.1 (23G93), but it's not only happening to me, other members are also experiencing this.

ottokruse commented 3 months ago

OK that should be good.

Tried this yet?

Passwordless.configure({
  cognitoIdpEndpoint: import.meta.env.VITE_COGNITO_IDP_ENDPOINT,
  clientId: import.meta.env.VITE_CLIENT_ID,
  fido2: {
    baseUrl: import.meta.env.VITE_FIDO2_BASE_URL,
    authenticatorSelection: {
      userVerification: "required",
      requireResidentKey: true, // added
      residentKey: "required", // added
    },
  },
  debug: console.debug,
});
juanf9224 commented 3 months ago

OK that should be good.

Tried this yet?

Passwordless.configure({
  cognitoIdpEndpoint: import.meta.env.VITE_COGNITO_IDP_ENDPOINT,
  clientId: import.meta.env.VITE_CLIENT_ID,
  fido2: {
    baseUrl: import.meta.env.VITE_FIDO2_BASE_URL,
    authenticatorSelection: {
      userVerification: "required",
      requireResidentKey: true, // added
      residentKey: "required", // added
    },
  },
  debug: console.debug,
});

Thanks for the prompt responses @ottokruse haven’t tried with the requireResidentKey prop, only with residentKey one, will try it and post the update here.

juanf9224 commented 3 months ago

@ottokruse I tried with above suggestion but got same error, I did notice in the lib, when invoking fido2GetCredential it only uses the userVerification property when creating the publicKey object:

async function fido2getCredential({ relyingPartyId, challenge, credentials, timeout, userVerification, }) {
    const { debug, fido2: { extensions } = {} } = configure();
    const publicKey = {
        challenge: bufferFromBase64Url(challenge),
        allowCredentials: credentials?.map((credential) => ({
            id: bufferFromBase64Url(credential.id),
            transports: credential.transports,
            type: "public-key",
        })),
        timeout,
        userVerification,
        rpId: relyingPartyId,
        extensions,
    };
 // Rest of code...

Even if I set it in the initial config it won't be used here, why isn't using theauthenticatorSelection object instead here?

ottokruse commented 3 months ago

authenticatorSelection is not a valid field for navigator.credentials.get(), see https://www.w3.org/TR/webauthn-2/#dictdef-publickeycredentialrequestoptions.

It is a valid field for navigator.credentials.create(), see https://www.w3.org/TR/webauthn-2/#dictdef-publickeycredentialcreationoptions

What exactly is your situation? You can create the passkey, but not sign-in with it? Or the create already fails?

juanf9224 commented 3 months ago

authenticatorSelection is not a valid field for navigator.credentials.get(), see https://www.w3.org/TR/webauthn-2/#dictdef-publickeycredentialrequestoptions.

It is a valid field for navigator.credentials.create(), see https://www.w3.org/TR/webauthn-2/#dictdef-publickeycredentialcreationoptions

What exactly is your situation? You can create the passkey, but not sign-in with it? Or the create already fails?

@ottokruse thanks for the references.

Yeah, that's our exact issue, we can create it but not sign in with it.

ottokruse commented 3 months ago

I just tried the end-to-end example again on Safari 17.6 (19618.3.11.11.5) on MacOS Sonoma 14.6.1 and it works like a charm for me, both for my Mac's touch as with a Yubikey.

That's with this config, unchanged per the end-to-end example:

Passwordless.configure({
  cognitoIdpEndpoint: import.meta.env.VITE_COGNITO_IDP_ENDPOINT,
  clientId: import.meta.env.VITE_CLIENT_ID,
  fido2: {
    baseUrl: import.meta.env.VITE_FIDO2_BASE_URL,
    authenticatorSelection: {
      userVerification: "required",
    },
  },
  debug: console.debug,
});

Can you also try to deploy the end-to-end example and try that one?

Otherwise send me your email address please I can add you to my instance as well as user. Then we can try you creating a passkey and logging in with that to my sample deployment and I can have a look in dynamodb at what's different between your fido credentials and mine.

juanf9224 commented 2 months ago

@ottokruse Apologies for the long delay, could you add this email? juanf.legacy@gmail.com

ottokruse commented 2 months ago

Done, sent you an SES verification mail (please click the link) and then you can sign in here with a magic link, after which you can create a passkey and sign in with that: https://dgn579m3u5x3n.cloudfront.net/

juanf9224 commented 2 months ago

@ottokruse Thanks, I tried it, yours is working fine but mine is not, I can't see why, both publickKey objects that are passed to the navigatior.credentials.get look the same:

this is from your instance: image

this one is from mine: image

Getting same error trace:

image
ottokruse commented 2 months ago

I can't quickly find a source for this info but I remember safari has a quirk. Safari requires the webauthn API call to be made in an event directly triggered by the user, like a click. At which point and how are you invoking the Passwordless lib? In an onclick handler?

ottokruse commented 2 months ago

This post mentions it and I came across it myself as well earlier. https://www.linkedin.com/posts/timcappalli_introducing-passkey-support-to-fastmail-activity-7234553443154092032-1Y2T?utm_source=share&utm_medium=member_ios

ottokruse commented 2 months ago

Example, our React component uses an onClick handler: https://github.com/aws-samples/amazon-cognito-passwordless-auth/blob/263f0c168daddeae2311a99566ada8531d8d42a6/client/react/components.tsx#L233-L242

juanf9224 commented 2 months ago

@ottokruse Yeah, I'm invoking it inside an onclick handler from a Button, I have a function that handles the logic and invokes authenticateWithFido2, this is how I'm invoking it inside a handler function that I pass on the onClick prop:

await authenticateWithFido2({ username: watchedEmail?.trim()?.toLowerCase() }).signedIn

and I tried this way to see what happens but nothing happens, not even an error, only getting the error when using await and .signedIn:

image
ottokruse commented 2 months ago

Mmmmmm only difference is you don't pass credentials, just username. You're sure it has a value right? Just curious, could you try passing credentials: [] ?

ottokruse commented 2 months ago

Yes you can await the authenticateWithFido2().signedIn promise to get the error in your face bubbled up, or rely on the lastError variable in the hook.

juanf9224 commented 2 months ago

I did pass the credentials from the lastSignedUser from the useLocalUserCache hook to test but got same result, now tried with your suggestion of an empty array and got same result as well.

ottokruse commented 2 months ago

Frustrating!

I have no ideas left at this point.

Suggest to try the Webauthn calls directly (navigator.credentials.get()) and see if that works. If not--file the issue with Apple against Safari. If that does work, we'd need to figure out why our lib fails for you (but not in our own example, so weird!).

juanf9224 commented 2 months ago

@ottokruse thanks for all your help!, I'll try that and file the issue with them, as soon as I fix it I'll update here.

ottokruse commented 1 month ago

Any update mate?

juanf9224 commented 1 month ago

Any update mate?

Hey @ottokruse no update yet, I haven't been able to file the issue since I had been working on something else but will do next week and keep you posted.