frontend/passkeys.js (61 lines of code) (raw):
export async function registerPasskey(csrfToken) {
const response = await fetch('/passkey/registration-options');
const publicKeyCredentialCreationOptionsJSON = await response.json();
const credentialCreationOptions = PublicKeyCredential.parseCreationOptionsFromJSON(publicKeyCredentialCreationOptionsJSON);
const publicKeyCredential = await navigator.credentials.create({ publicKey: credentialCreationOptions });
const registrationResponseJSON = publicKeyCredential.toJSON();
await fetch('/passkey/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Csrf-Token': csrfToken
},
body: JSON.stringify(registrationResponseJSON)
});
}
export function setUpRegisterPasskeyButton(buttonSelector) {
const registerPasskeyButton = document.querySelector(buttonSelector);
registerPasskeyButton?.addEventListener('click', function (e) {
e.preventDefault();
const csrfToken = this.getAttribute('csrf-token');
registerPasskey(csrfToken).catch(function (err) {
console.error(err);
});
});
}
export async function authenticatePasskey(targetHref, csrfToken) {
console.log("starting authentication");
const authOptionsResponse = await fetch("/passkey/auth-options");
const publicKeyCredentialRequestOptionsJSON = await authOptionsResponse.json();
const credentialGetOptions = PublicKeyCredential.parseRequestOptionsFromJSON(publicKeyCredentialRequestOptionsJSON);
const publicKeyCredential = await navigator.credentials.get({ publicKey: credentialGetOptions});
console.log("publicKeyCredential: ", publicKeyCredential);
// Add a form to the DOM so that it can be submitted at page level
const form = document.createElement('form');
form.setAttribute('method', 'post');
form.setAttribute('action', targetHref);
const credsInput = document.createElement('input');
credsInput.setAttribute('type','hidden');
credsInput.setAttribute('name','credentials');
credsInput.setAttribute('value', JSON.stringify(publicKeyCredential.toJSON()));
const csrfTokenInput = document.createElement('input');
csrfTokenInput.setAttribute('type','hidden');
csrfTokenInput.setAttribute('name','csrfToken');
csrfTokenInput.setAttribute('value', csrfToken);
form.appendChild(credsInput);
form.appendChild(csrfTokenInput);
document.getElementsByTagName('body')[0].appendChild(form);
form.submit();
}
export function setUpProtectedLinks(links) {
links.forEach((link) => {
link.addEventListener('click', function (e) {
e.preventDefault();
console.log('clicked');
const csrfToken = link.getAttribute('csrf-token');
const targetHref = link.href;
authenticatePasskey(targetHref, csrfToken).catch(function (err) {
console.error(err);
});
});
});
}