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
454 stars 53 forks source link

Base64 encoding challenge a requirement? #62

Closed mcm-ham closed 3 months ago

mcm-ham commented 3 months ago

Just trying out this library but got an error from util.parseBase64url with atob saying input is invalid. I see client.register is calling this function, after base64 encoding the challenge i.e. btoa it now works. Is it a requirement that challenge is base64 encoded if so should this be mentioned in the docs or did I miss that somewhere?

dagnelies commented 3 months ago

This is actually a tricky one. I remember hesitating to do it, then forgot.

The main concern is that if you use another lib server side, it expects bytes as challenge. Now, if you encode/decode from string to bytes, the next question is what character encoding to use. So it should state in the fine print that it uses UTF-8 to encode the string into bytes, that is then base64 encoded again in the json payload. Confusing right? In the end, I opted it to be base64 encoded from the start.

I'll improve the docs

dagnelies commented 3 months ago

My previous message was kind of confusing. For the sake of clarity...

The underlying protocol expects "bytes" as challenges, and until now base64url is used everywhere for conversion between text and bytes. That's also what most other server-side libraries expect.

Now what would happen if I change the logic to "let's encode any text to bytes using UTF-8 instead"? Well, nothing much actually except two things. First, a slightly heterogenous handling of binary values. Secondly, more subtle, that servers need to adapt their logic slightly. For non-js ecosystem, they usually generate random bytes as challenge, send it as base64url, and verify these bytes in the registration/authentication payload. The server-side would have to adapt the logic into producing a text (which is rarely produced by a secure random), then use the UTF-8 encoded version of this text as bytes to verify against. That's fair enough, but it still increases the cognitive overhead.

mcm-ham commented 3 months ago

That makes sense, thanks for the explanation.

The other thing noted is https://webauthn.passwordless.id/authentication/#3-send-the-payload-to-the-server does not match the new value returned that aligns with JSON version of PublicKeyCredential.

dagnelies commented 3 months ago

I'll leave the challenge as base64url. However I leave this ticket open until I improved the docs. I'll also add a server.createChallenge() to make things easier/safer.

dagnelies commented 3 months ago

There was already utils.randomChallenge(). I moved it to server.randomChallenge() and documented it.