diff --git a/authentik/stages/authenticator_webauthn/stage.py b/authentik/stages/authenticator_webauthn/stage.py index d9c5a9ebb..071e0e52e 100644 --- a/authentik/stages/authenticator_webauthn/stage.py +++ b/authentik/stages/authenticator_webauthn/stage.py @@ -96,7 +96,6 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView): user_verification=str(stage.user_verification), ), ) - registration_options.user.id = user.uid self.request.session["challenge"] = registration_options.challenge return AuthenticatorWebAuthnChallenge( diff --git a/web/src/flows/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage.ts b/web/src/flows/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage.ts index 64d23b0ef..6ffe9b755 100644 --- a/web/src/flows/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage.ts +++ b/web/src/flows/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage.ts @@ -51,6 +51,7 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage< // byte arrays as expected by the spec. const publicKeyCredentialCreateOptions = transformCredentialCreateOptions( this.challenge?.registration as PublicKeyCredentialCreationOptions, + this.challenge?.registration.user.id, ); // request the authenticator(s) to create a new credential keypair. diff --git a/web/src/flows/stages/authenticator_webauthn/utils.ts b/web/src/flows/stages/authenticator_webauthn/utils.ts index 77be2556e..01b1f05f6 100644 --- a/web/src/flows/stages/authenticator_webauthn/utils.ts +++ b/web/src/flows/stages/authenticator_webauthn/utils.ts @@ -8,15 +8,26 @@ export function b64RawEnc(buf: Uint8Array): string { return base64js.fromByteArray(buf).replace(/\+/g, "-").replace(/\//g, "_"); } +export function u8arr(input: string): Uint8Array { + return Uint8Array.from(atob(input.replace(/_/g, "/").replace(/-/g, "+")), (c) => + c.charCodeAt(0), + ); +} + /** * Transforms items in the credentialCreateOptions generated on the server * into byte arrays expected by the navigator.credentials.create() call */ export function transformCredentialCreateOptions( credentialCreateOptions: PublicKeyCredentialCreationOptions, + userId: string, ): PublicKeyCredentialCreationOptions { const user = credentialCreateOptions.user; - user.id = u8arr(b64enc(credentialCreateOptions.user.id as Uint8Array)); + // Because json can't contain raw bytes, the server base64-encodes the User ID + // So to get the base64 encoded byte array, we first need to convert it to a regular + // string, then a byte array, re-encode it and wrap that in an array. + const stringId = decodeURIComponent(escape(window.atob(userId))); + user.id = u8arr(b64enc(u8arr(stringId))); const challenge = u8arr(credentialCreateOptions.challenge.toString()); const transformedCredentialCreateOptions = Object.assign({}, credentialCreateOptions, { @@ -63,12 +74,6 @@ export function transformNewAssertionForServer(newAssertion: PublicKeyCredential }; } -function u8arr(input: string): Uint8Array { - return Uint8Array.from(atob(input.replace(/_/g, "/").replace(/-/g, "+")), (c) => - c.charCodeAt(0), - ); -} - export function transformCredentialRequestOptions( credentialRequestOptions: PublicKeyCredentialRequestOptions, ): PublicKeyCredentialRequestOptions {