- 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