import { action } from '@ember/object'; import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; import '@simplewebauthn/browser'; import { startAuthentication, startRegistration, } from '@simplewebauthn/browser'; export interface PasskeySignature { // The arguments accepted by the component Args: {}; // Any blocks yielded by the component Blocks: { default: []; }; // The element to which `...attributes` is applied in the component template Element: null; } export default class Passkey extends Component { @tracked username: string = ''; @tracked error: string | undefined; @action async startLogin() { try { // Get login options from your server. Here, we also receive the challenge. const response = await fetch('/webauthn/passkey/loginBegin', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: this.username }), }); // Check if the login options are ok. if (!response.ok) { const msg = await response.json(); throw new Error('Failed to get login options from server: ' + msg); } // Convert the login options to JSON. const options = await response.json(); // This triggers the browser to display the passkey / WebAuthn modal (e.g. Face ID, Touch ID, Windows Hello). // A new assertionResponse is created. This also means that the challenge has been signed. const assertionResponse = await startAuthentication(options.publicKey); // Send assertionResponse back to server for verification. const verificationResponse = await fetch( '/webauthn/passkey/loginFinish', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(assertionResponse), }, ); const msg = await verificationResponse.json(); if (verificationResponse.ok) { this.error = undefined; } else { this.error = msg; } } catch (error: any) { this.error = 'Error: ' + error.message; } } @action async startRegistration() { try { // Get registration options from your server. Here, we also receive the challenge. const response = await fetch('/webauthn/passkey/registerBegin', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: this.username }), }); // Check if the registration options are ok. if (!response.ok) { const msg = await response.json(); throw new Error( 'User already exists or failed to get registration options from server: ' + msg, ); } // Convert the registration options to JSON. const options = await response.json(); console.log('registration start', options); // This triggers the browser to display the passkey / WebAuthn modal (e.g. Face ID, Touch ID, Windows Hello). // A new attestation is created. This also means a new public-private-key pair is created. const attestationResponse = await startRegistration(options.publicKey); console.log('Attempting to complete registration', attestationResponse); // Send attestationResponse back to server for verification and storage. const verificationResponse = await fetch( '/webauthn/passkey/registerFinish', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(attestationResponse), }, ); const msg = await verificationResponse.json(); if (verificationResponse.ok) { this.error = undefined; } else { this.error = msg; } } catch (error: any) { this.error = 'Error: ' + error.message; } } }