diff --git a/app/javascript/packages/webauthn/enroll-webauthn-device.spec.ts b/app/javascript/packages/webauthn/enroll-webauthn-device.spec.ts index 16ab7c5c629..449e8d65802 100644 --- a/app/javascript/packages/webauthn/enroll-webauthn-device.spec.ts +++ b/app/javascript/packages/webauthn/enroll-webauthn-device.spec.ts @@ -61,8 +61,6 @@ describe('enrollWebauthnDevice', () => { user, challenge, excludeCredentials, - authenticatorAttachment: 'cross-platform', - hints: ['security-key'], }); expect(navigator.credentials.create).to.have.been.calledWith({ @@ -130,16 +128,15 @@ describe('enrollWebauthnDevice', () => { context('platform authenticator', () => { it('enrolls a device with correct authenticatorAttachment', async () => { await enrollWebauthnDevice({ + platformAuthenticator: true, user, challenge, excludeCredentials, - authenticatorAttachment: 'platform', - hints: ['client-device'], }); expect(navigator.credentials.create).to.have.been.calledWithMatch({ publicKey: { - hints: ['client-device'], + hints: undefined, authenticatorSelection: { authenticatorAttachment: 'platform', }, @@ -162,7 +159,6 @@ describe('enrollWebauthnDevice', () => { user, challenge, excludeCredentials, - authenticatorAttachment: 'cross-platform', }); expect(result.transports).to.equal(undefined); @@ -182,7 +178,6 @@ describe('enrollWebauthnDevice', () => { user, challenge, excludeCredentials, - authenticatorAttachment: 'cross-platform', }); expect(result.authenticatorDataFlagsValue).to.equal(undefined); diff --git a/app/javascript/packages/webauthn/enroll-webauthn-device.ts b/app/javascript/packages/webauthn/enroll-webauthn-device.ts index bf19d2a75db..f853488173e 100644 --- a/app/javascript/packages/webauthn/enroll-webauthn-device.ts +++ b/app/javascript/packages/webauthn/enroll-webauthn-device.ts @@ -18,15 +18,13 @@ interface AuthenticatorAttestationResponseBrowserSupport type PublicKeyCredentialHintType = 'client-device' | 'security-key' | 'hybrid'; interface EnrollOptions { + platformAuthenticator?: boolean; + user: PublicKeyCredentialUserEntity; challenge: BufferSource; excludeCredentials: PublicKeyCredentialDescriptor[]; - - authenticatorAttachment?: AuthenticatorAttachment; - - hints?: Array; } interface EnrollResult { @@ -80,11 +78,10 @@ const SUPPORTED_ALGORITHMS: COSEAlgorithm[] = [ ]; async function enrollWebauthnDevice({ + platformAuthenticator = false, user, challenge, excludeCredentials, - authenticatorAttachment, - hints, }: EnrollOptions): Promise { const credential = (await navigator.credentials.create({ publicKey: { @@ -94,11 +91,16 @@ async function enrollWebauthnDevice({ pubKeyCredParams: SUPPORTED_ALGORITHMS.map((alg) => ({ alg, type: 'public-key' })), timeout: 800000, attestation: 'none', - hints, + hints: platformAuthenticator ? undefined : ['security-key'], authenticatorSelection: { - // Prevents user from needing to use PIN with Security Key + // A user is assumed to be AAL2 recently authenticated before being permitted to add an + // authentication method to their account. Additionally, unless explicitly discouraged, + // Windows devices will prompt the user to add a PIN to their security key. When used as a + // single-factor authenticator in combination with a memorized secret (password), proving + // possession is sufficient, and a PIN ("something you know") is unnecessary friction that + // contributes to abandonment or loss of access. userVerification: 'discouraged', - authenticatorAttachment, + authenticatorAttachment: platformAuthenticator ? 'platform' : 'cross-platform', }, excludeCredentials, } as PublicKeyCredentialCreationOptionsWithHints, diff --git a/app/javascript/packs/webauthn-setup.ts b/app/javascript/packs/webauthn-setup.ts index 743b99c827c..b0c2d0c9eff 100644 --- a/app/javascript/packs/webauthn-setup.ts +++ b/app/javascript/packs/webauthn-setup.ts @@ -42,6 +42,7 @@ function webauthn() { (document.getElementById('platform_authenticator') as HTMLInputElement).value === 'true'; enrollWebauthnDevice({ + platformAuthenticator, user: { id: longToByteArray(Number((document.getElementById('user_id') as HTMLInputElement).value)), name: (document.getElementById('user_email') as HTMLInputElement).value, @@ -55,8 +56,6 @@ function webauthn() { .split(',') .filter(Boolean), ), - authenticatorAttachment: platformAuthenticator ? 'platform' : 'cross-platform', - hints: platformAuthenticator ? ['client-device', 'hybrid'] : ['security-key'], }) .then((result) => { (document.getElementById('webauthn_id') as HTMLInputElement).value = result.webauthnId;