passwordless-id / webauthn

Webauthn / passkeys helper library to make your life easier. Client side, server side and demo included.
https://webauthn.passwordless.id
MIT License
463 stars 54 forks source link

client.register() 'domain' option doesn't seem to work #81

Open knutesears opened 5 days ago

knutesears commented 5 days ago

Got this error on my server:

Error: Unexpected ClientData origin: https://www.txtgms.com

...when it was working fine without the www subdomain. Changed client code to:

        const pk_registration = await webAuthClient.register({
            user: {
                name: uname,                          // lc, canonical
                displayName: displayName,   // users full name || non lc'd username
            },
            challenge: pk_nonce,
            domain: 'txtgms.com',            // www.txtgms.com from window.location.hostname was making it choke

            /* possibly other options; see:  https://webauthn.passwordless.id/registration/  */
        })

Still get the same error. So, grabbed pk_registration.response.clientDataJSON:

"eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiV1RHa2FHVU9MUE5JU1VNOE45R1NjRTlZIiwib3JpZ2luIjoiaHR0cHM6Ly93d3cudHh0Z21zLmNvbSIsImNyb3NzT3JpZ2luIjpmYWxzZX0="

...decoded it to:

{"type":"webauthn.create","challenge":"WTGkaGUOLPNISUM8N9GScE9Y","origin":"https://www.txtgms.com","crossOrigin":false}

It would appear that register() is not acting on the 'domain' option at all. I note that the registration client data is using the 'origin', which includes the protocol. Maybe I'll look at the module source, but at a minimum the docs are wrong.

dagnelies commented 1 day ago

To tell the truth, this option might indeed be buggy. It's the only option not properly tested😳. I'll need to dig into, but I'm short on time right now.

dagnelies commented 1 day ago

At first sight, the registration correct but the verification is wrong. To test it, use another subdomain and it should be able to use the same passkeys. In other words, using a page on a.txtgms.com and b.txtgms.com should be able to use the same passkeys if registered with parent domain txtgms.com. ...once I fix the verification 🙄

knutesears commented 1 day ago

Thx for checking it out. Not a biggie to me, just thought I should report it. Started when my friend typed www.site.com into browser -- who does that anymore? I fixed that with a hammer by just redirecting www.domain to domain, but I can imagine some people would want to issue keys on www.site.com and have them be valid for site.com -- but maybe that's a security issue. Keys for site.com should work on sub.site.com, but not the other way around, correct? And www. is just a special case of that.

p.s. Followed the discussion at https://github.com/w3c/webauthn/issues/1749 and totally agree with you. It's obtuse that the API won't even give a passkey existence indicator for the selfsame web site. I have an id (browser_id = ${ip_addr}-${timestamp}) that replicates between localStorage and Cookies, so at least I have a shot of knowing if the user has a passkey on that browser.

dagnelies commented 1 day ago

The default is fixed on the exact domain it is used. So that a passkey generated at "sub.example.com" is only valid for this subdomain, not "example.com" nor "child.sub.example.com".

The domain parameter can be used to reference a parent domain. For example "auth.example.com" could set domain to "example.com" during registration. That way, any authentication in a subdomain "whatever.example.com" could also use the same passkey (by setting "example.com" as domain too). ...if it worked properly that is.

knutesears commented 21 hours ago

Thank you for the clarification -- I think I get it now.

And thank you for making this library. I shopped around, and yours is one of the cleanest (and not written by some company trying to sell some subscription-based passkey snakeoil;)