- From: Matthew Miller via GitHub <sysbot+gh@w3.org>
- Date: Wed, 10 Jan 2024 05:56:59 +0000
- To: public-webauthn@w3.org
MasterKale has just created a new issue for https://github.com/w3c/webauthn: == Refine JSON serialization to use UTF-8 encoding for `user.id` and `userHandle` == ## Proposed Change The JSON serialization logic we added in L3 consistently uses base64url encoding for any value that's an `ArrayBuffer` in the browser. However, in my experience it makes for easier debugging during authentication when UTF-8 encoding is used specifically for `user.id` and `userHandle` as **it helps developers immediately see the user ID in a recognizable format**. For example, consider an RP that generates its own string identifiers for users: ```js const userID = 'USER2ML8P7C08R'; ``` An RP dev may **naively** specify this value for `user.id` when calling `parseCreationOptionsFromJSON()` believing, "it's non-PII so why not?" ```js var opts = PublicKeyCredential.parseCreationOptionsFromJSON({ // ... user: { id: userID, name: 'TestUser', displayName: 'TestUser' }, }); const credential = await navigator.credentials.get({ publicKey: opts }); ``` If the RP commits to the spec's JSON serialization methods during a subsequent authentication then they'll experience an unintuitive footgun: ```js const opts = PublicKeyCredential.parseRequestOptionsFromJSON({ ... }); const credential = await navigator.credentials.get({ publicKey: opts }); const credentialJSON = credential.toJSON(); console.log(userID); // USER2ML8P7C08R console.log(credentialJSON.response.userHandle) // USER2ML8P7C08Q console.log(userID === credentialJSON.response.userHandle); // false ``` The user ID gets [munged](https://en.wikipedia.org/wiki/Mung_(computer_term))! The `userHandle` becomes unusable, and the dev falls back to using credential ID to determine which user should be logged in (which is a bad idea as @sbweeden re-confirmed recently in https://github.com/w3c/webauthn/issues/1909#issuecomment-1608648459) In my opinion (gleaned through practical experience) if `userHandle` is the same then it's easy to pull that value out of the front end and cross-reference it when pulling database records, query logging for the value, use and compare the value programmatically in other areas of the product...currently RP devs (that **correctly** base64url-encoded the UTF-8 bytes in `"USER2ML8P7C08R"` as per the current text in L3 and specify `user.id` as `"VVNFUjJNTDhQN0MwOFI"` instead) have to base64url-decode `userHandle` to bytes and then UTF-8 encode those bytes to get back to `"USER2ML8P7C08R"` before continuing with their troubleshooting. I therefore assert that it is more useful to RP devs for `userHandle` out of a call to `toJSON()` to be the same as the `user.id` string passed into `parseCreationOptionsFromJSON()`. We can remove this footgun by **updating the JSON serialization logic so that the `user.id` argument is allowed to be any UTF-8 string when calling `parseCreationOptionsFromJSON()`, and that UTF-8 encoding is used to serialize `userHandle` when `toJSON()` is called.** ## Potential Impact While trying to find a browser I could run sample code in to put this all together I noticed that only **Firefox** currently has fully implemented the JSON serialization methods. **Chrome** currently only supports `parseCreationOptionsFromJSON()` and `toJSON()`, while **Safari** doesn't currently support any of the methods. I'm hoping this means there's still a chance to discuss updating this logic, as it would mean breaking any current use of these methods. ## Footgun Repro ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <button id="startReg">Step 1: Call .create()</button> <button id="startAuth">Step 2: Call .get()</button> <script> /** * `user.id` and `userHandle` will not match with this human-readable value */ const userID = "USER2ML8P7C08R"; /** * This `userID` is the base64url-encoded UTF-8 bytes in the string above. `user.id` and * `userHandle` will match when this value is used instead. */ // const userID = "VVNFUjJNTDhQN0MwOFI"; // Values we'll use during auth let credIdBase64URL = undefined; let credTransports = []; document.getElementById("startReg").addEventListener("click", async () => { const opts = PublicKeyCredential.parseCreationOptionsFromJSON({ user: { id: userID, name: "TestUser", displayName: "TestUser"}, rp: { name: "localhost" }, challenge: "AAAA", pubKeyCredParams: [ { type: "public-key", alg: -7 }, { type: "public-key", alg: -257 }], }); const credential = await navigator.credentials.create({ publicKey: opts }); const credentialJSON = credential.toJSON(); credIdBase64URL = credentialJSON.id; credTransports = credentialJSON.response.transports; }); document.getElementById("startAuth").addEventListener("click", async () => { const opts = PublicKeyCredential.parseRequestOptionsFromJSON({ challenge: "AAAA", allowCredentials: [{ id: credIdBase64URL, transports: credTransports, type: "public-key" }], }); const credential = await navigator.credentials.get({ publicKey: opts }); const credentialJSON = credential.toJSON(); console.log("user.id:", userID); console.log("userHandle:", credentialJSON.response.userHandle); console.log(userID === credentialJSON.response.userHandle); }); </script> </body> </html> ``` Please view or discuss this issue at https://github.com/w3c/webauthn/issues/2013 using your GitHub account -- Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config
Received on Wednesday, 10 January 2024 05:57:01 UTC