Skip to content

Commit 2e12161

Browse files
zeripathwxiaoguanglunny
authored
Fix bugs with WebAuthn preventing sign in and registration. (#22651) (#22721)
Partial Backport #22651 This PR fixes a longstanding bug within webauthn due to the backend using URLEncodedBase64 but the javascript using decoding using plain base64. This causes intermittent issues with users reporting decoding errors. Fix #22507 Signed-off-by: Andrew Thornton <[email protected]> Co-authored-by: wxiaoguang <[email protected]> Co-authored-by: Lunny Xiao <[email protected]>
1 parent 9cde526 commit 2e12161

File tree

1 file changed

+22
-15
lines changed

1 file changed

+22
-15
lines changed

web_src/js/features/user-auth-webauthn.js

+22-15
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ export function initUserAuthWebAuthn() {
1414

1515
$.getJSON(`${appSubUrl}/user/webauthn/assertion`, {})
1616
.done((makeAssertionOptions) => {
17-
makeAssertionOptions.publicKey.challenge = decode(makeAssertionOptions.publicKey.challenge);
17+
makeAssertionOptions.publicKey.challenge = decodeURLEncodedBase64(makeAssertionOptions.publicKey.challenge);
1818
for (let i = 0; i < makeAssertionOptions.publicKey.allowCredentials.length; i++) {
19-
makeAssertionOptions.publicKey.allowCredentials[i].id = decode(makeAssertionOptions.publicKey.allowCredentials[i].id);
19+
makeAssertionOptions.publicKey.allowCredentials[i].id = decodeURLEncodedBase64(makeAssertionOptions.publicKey.allowCredentials[i].id);
2020
}
2121
navigator.credentials.get({
2222
publicKey: makeAssertionOptions.publicKey
@@ -56,14 +56,14 @@ function verifyAssertion(assertedCredential) {
5656
type: 'POST',
5757
data: JSON.stringify({
5858
id: assertedCredential.id,
59-
rawId: bufferEncode(rawId),
59+
rawId: encodeURLEncodedBase64(rawId),
6060
type: assertedCredential.type,
6161
clientExtensionResults: assertedCredential.getClientExtensionResults(),
6262
response: {
63-
authenticatorData: bufferEncode(authData),
64-
clientDataJSON: bufferEncode(clientDataJSON),
65-
signature: bufferEncode(sig),
66-
userHandle: bufferEncode(userHandle),
63+
authenticatorData: encodeURLEncodedBase64(authData),
64+
clientDataJSON: encodeURLEncodedBase64(clientDataJSON),
65+
signature: encodeURLEncodedBase64(sig),
66+
userHandle: encodeURLEncodedBase64(userHandle),
6767
},
6868
}),
6969
contentType: 'application/json; charset=utf-8',
@@ -85,14 +85,21 @@ function verifyAssertion(assertedCredential) {
8585
});
8686
}
8787

88-
// Encode an ArrayBuffer into a base64 string.
89-
function bufferEncode(value) {
88+
// Encode an ArrayBuffer into a URLEncoded base64 string.
89+
function encodeURLEncodedBase64(value) {
9090
return encode(value)
9191
.replace(/\+/g, '-')
9292
.replace(/\//g, '_')
9393
.replace(/=/g, '');
9494
}
9595

96+
// Dccode a URLEncoded base64 to an ArrayBuffer string.
97+
function decodeURLEncodedBase64(value) {
98+
return decode(value
99+
.replace(/_/g, '/')
100+
.replace(/-/g, '+'));
101+
}
102+
96103
function webauthnRegistered(newCredential) {
97104
const attestationObject = new Uint8Array(newCredential.response.attestationObject);
98105
const clientDataJSON = new Uint8Array(newCredential.response.clientDataJSON);
@@ -104,11 +111,11 @@ function webauthnRegistered(newCredential) {
104111
headers: {'X-Csrf-Token': csrfToken},
105112
data: JSON.stringify({
106113
id: newCredential.id,
107-
rawId: bufferEncode(rawId),
114+
rawId: encodeURLEncodedBase64(rawId),
108115
type: newCredential.type,
109116
response: {
110-
attestationObject: bufferEncode(attestationObject),
111-
clientDataJSON: bufferEncode(clientDataJSON),
117+
attestationObject: encodeURLEncodedBase64(attestationObject),
118+
clientDataJSON: encodeURLEncodedBase64(clientDataJSON),
112119
},
113120
}),
114121
dataType: 'json',
@@ -184,11 +191,11 @@ function webAuthnRegisterRequest() {
184191
}).done((makeCredentialOptions) => {
185192
$('#nickname').closest('div.field').removeClass('error');
186193

187-
makeCredentialOptions.publicKey.challenge = decode(makeCredentialOptions.publicKey.challenge);
188-
makeCredentialOptions.publicKey.user.id = decode(makeCredentialOptions.publicKey.user.id);
194+
makeCredentialOptions.publicKey.challenge = decodeURLEncodedBase64(makeCredentialOptions.publicKey.challenge);
195+
makeCredentialOptions.publicKey.user.id = decodeURLEncodedBase64(makeCredentialOptions.publicKey.user.id);
189196
if (makeCredentialOptions.publicKey.excludeCredentials) {
190197
for (let i = 0; i < makeCredentialOptions.publicKey.excludeCredentials.length; i++) {
191-
makeCredentialOptions.publicKey.excludeCredentials[i].id = decode(makeCredentialOptions.publicKey.excludeCredentials[i].id);
198+
makeCredentialOptions.publicKey.excludeCredentials[i].id = decodeURLEncodedBase64(makeCredentialOptions.publicKey.excludeCredentials[i].id);
192199
}
193200
}
194201

0 commit comments

Comments
 (0)