Skip to content

Commit e86c40a

Browse files
authored
web/flow: cleanup WebAuthn helper functions (#14460)
* pass #1 Signed-off-by: Jens Langhammer <jens@goauthentik.io> * pass #2 Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add polyfill Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
1 parent 20e0748 commit e86c40a

File tree

8 files changed

+76
-323
lines changed

8 files changed

+76
-323
lines changed

web/package-lock.json

Lines changed: 54 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@
105105
"@sentry/browser": "^9.28.1",
106106
"@spotlightjs/spotlight": "^3.0.0",
107107
"@webcomponents/webcomponentsjs": "^2.8.0",
108-
"base64-js": "^1.5.1",
109108
"change-case": "^5.4.4",
110109
"chart.js": "^4.4.9",
111110
"chartjs-adapter-date-fns": "^3.0.0",
@@ -137,6 +136,7 @@
137136
"trusted-types": "^2.0.0",
138137
"ts-pattern": "^5.7.1",
139138
"unist-util-visit": "^5.0.0",
139+
"webauthn-polyfills": "^0.1.7",
140140
"webcomponent-qr-code": "^1.2.0",
141141
"yaml": "^2.8.0"
142142
},

web/packages/sfe/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@
1111
},
1212
"dependencies": {
1313
"@goauthentik/api": "^2024.6.0-1719577139",
14-
"base64-js": "^1.5.1",
1514
"bootstrap": "^4.6.1",
1615
"formdata-polyfill": "^4.0.10",
1716
"jquery": "^3.7.1",
18-
"weakmap-polyfill": "^2.0.4"
17+
"weakmap-polyfill": "^2.0.4",
18+
"webauthn-polyfills": "^0.1.7"
1919
},
2020
"devDependencies": {
2121
"@goauthentik/core": "^1.0.0",

web/packages/sfe/src/index.ts

Lines changed: 4 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { fromByteArray } from "base64-js";
21
import "formdata-polyfill";
32
import $ from "jquery";
43
import "weakmap-polyfill";
4+
import "webauthn-polyfills";
55

66
import {
77
type AuthenticatorValidationChallenge,
@@ -257,47 +257,9 @@ class AutosubmitStage extends Stage<AutosubmitChallenge> {
257257
}
258258
}
259259

260-
export interface Assertion {
261-
id: string;
262-
rawId: string;
263-
type: string;
264-
registrationClientExtensions: string;
265-
response: {
266-
clientDataJSON: string;
267-
attestationObject: string;
268-
};
269-
}
270-
271-
export interface AuthAssertion {
272-
id: string;
273-
rawId: string;
274-
type: string;
275-
assertionClientExtensions: string;
276-
response: {
277-
clientDataJSON: string;
278-
authenticatorData: string;
279-
signature: string;
280-
userHandle: string | null;
281-
};
282-
}
283-
284260
class AuthenticatorValidateStage extends Stage<AuthenticatorValidationChallenge> {
285261
deviceChallenge?: DeviceChallenge;
286262

287-
b64enc(buf: Uint8Array): string {
288-
return fromByteArray(buf).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
289-
}
290-
291-
b64RawEnc(buf: Uint8Array): string {
292-
return fromByteArray(buf).replace(/\+/g, "-").replace(/\//g, "_");
293-
}
294-
295-
u8arr(input: string): Uint8Array {
296-
return Uint8Array.from(atob(input.replace(/_/g, "/").replace(/-/g, "+")), (c) =>
297-
c.charCodeAt(0),
298-
);
299-
}
300-
301263
checkWebAuthnSupport(): boolean {
302264
if ("credentials" in navigator) {
303265
return true;
@@ -310,98 +272,6 @@ class AuthenticatorValidateStage extends Stage<AuthenticatorValidationChallenge>
310272
return false;
311273
}
312274

313-
/**
314-
* Transforms items in the credentialCreateOptions generated on the server
315-
* into byte arrays expected by the navigator.credentials.create() call
316-
*/
317-
transformCredentialCreateOptions(
318-
credentialCreateOptions: PublicKeyCredentialCreationOptions,
319-
userId: string,
320-
): PublicKeyCredentialCreationOptions {
321-
const user = credentialCreateOptions.user;
322-
// Because json can't contain raw bytes, the server base64-encodes the User ID
323-
// So to get the base64 encoded byte array, we first need to convert it to a regular
324-
// string, then a byte array, re-encode it and wrap that in an array.
325-
const stringId = decodeURIComponent(window.atob(userId));
326-
user.id = this.u8arr(this.b64enc(this.u8arr(stringId)));
327-
const challenge = this.u8arr(credentialCreateOptions.challenge.toString());
328-
329-
return Object.assign({}, credentialCreateOptions, {
330-
challenge,
331-
user,
332-
});
333-
}
334-
335-
/**
336-
* Transforms the binary data in the credential into base64 strings
337-
* for posting to the server.
338-
* @param {PublicKeyCredential} newAssertion
339-
*/
340-
transformNewAssertionForServer(newAssertion: PublicKeyCredential): Assertion {
341-
const attObj = new Uint8Array(
342-
(newAssertion.response as AuthenticatorAttestationResponse).attestationObject,
343-
);
344-
const clientDataJSON = new Uint8Array(newAssertion.response.clientDataJSON);
345-
const rawId = new Uint8Array(newAssertion.rawId);
346-
347-
const registrationClientExtensions = newAssertion.getClientExtensionResults();
348-
return {
349-
id: newAssertion.id,
350-
rawId: this.b64enc(rawId),
351-
type: newAssertion.type,
352-
registrationClientExtensions: JSON.stringify(registrationClientExtensions),
353-
response: {
354-
clientDataJSON: this.b64enc(clientDataJSON),
355-
attestationObject: this.b64enc(attObj),
356-
},
357-
};
358-
}
359-
360-
transformCredentialRequestOptions(
361-
credentialRequestOptions: PublicKeyCredentialRequestOptions,
362-
): PublicKeyCredentialRequestOptions {
363-
const challenge = this.u8arr(credentialRequestOptions.challenge.toString());
364-
365-
const allowCredentials = (credentialRequestOptions.allowCredentials || []).map(
366-
(credentialDescriptor) => {
367-
const id = this.u8arr(credentialDescriptor.id.toString());
368-
return Object.assign({}, credentialDescriptor, { id });
369-
},
370-
);
371-
372-
return Object.assign({}, credentialRequestOptions, {
373-
challenge,
374-
allowCredentials,
375-
});
376-
}
377-
378-
/**
379-
* Encodes the binary data in the assertion into strings for posting to the server.
380-
* @param {PublicKeyCredential} newAssertion
381-
*/
382-
transformAssertionForServer(newAssertion: PublicKeyCredential): AuthAssertion {
383-
const response = newAssertion.response as AuthenticatorAssertionResponse;
384-
const authData = new Uint8Array(response.authenticatorData);
385-
const clientDataJSON = new Uint8Array(response.clientDataJSON);
386-
const rawId = new Uint8Array(newAssertion.rawId);
387-
const sig = new Uint8Array(response.signature);
388-
const assertionClientExtensions = newAssertion.getClientExtensionResults();
389-
390-
return {
391-
id: newAssertion.id,
392-
rawId: this.b64enc(rawId),
393-
type: newAssertion.type,
394-
assertionClientExtensions: JSON.stringify(assertionClientExtensions),
395-
396-
response: {
397-
clientDataJSON: this.b64RawEnc(clientDataJSON),
398-
signature: this.b64RawEnc(sig),
399-
authenticatorData: this.b64RawEnc(authData),
400-
userHandle: null,
401-
},
402-
};
403-
}
404-
405275
render() {
406276
if (this.challenge.deviceChallenges.length === 1) {
407277
this.deviceChallenge = this.challenge.deviceChallenges[0];
@@ -505,24 +375,18 @@ class AuthenticatorValidateStage extends Stage<AuthenticatorValidationChallenge>
505375
`);
506376
navigator.credentials
507377
.get({
508-
publicKey: this.transformCredentialRequestOptions(
509-
this.deviceChallenge?.challenge as PublicKeyCredentialRequestOptions,
378+
publicKey: PublicKeyCredential.parseRequestOptionsFromJSON(
379+
this.deviceChallenge?.challenge as PublicKeyCredentialRequestOptionsJSON,
510380
),
511381
})
512382
.then((assertion) => {
513383
if (!assertion) {
514384
throw new Error("No assertion");
515385
}
516386
try {
517-
// we now have an authentication assertion! encode the byte arrays contained
518-
// in the assertion data as strings for posting to the server
519-
const transformedAssertionForServer = this.transformAssertionForServer(
520-
assertion as PublicKeyCredential,
521-
);
522-
523387
// post the assertion to the server for verification.
524388
this.executor.submit({
525-
webauthn: transformedAssertionForServer,
389+
webauthn: (assertion as PublicKeyCredential).toJSON(),
526390
});
527391
} catch (err) {
528392
throw new Error(`Error when validating assertion on server: ${err}`);

0 commit comments

Comments
 (0)