feat: update login UI and JS for usernameless WebAuthn authentication

This commit is contained in:
Johan Lundberg 2026-02-17 13:42:35 +01:00
parent 32567b5484
commit ebe5497879
No known key found for this signature in database
GPG key ID: A6C152738D03C7D1
3 changed files with 24 additions and 27 deletions

View file

@ -1 +1 @@
{"keys": [{"kty": "RSA", "use": "sig", "kid": "yecGJYHchQnJbz3K39V9KOyVLez8gS0H8rTCANPFumQ", "n": "nS_gIt--OOcboxtT5SS72quz8ajGlcPW4IYrVCMaiSTKBqYRWjf0MdaRLtq1LHlwKoyu14akwfk2x3IH0Wq76NNpXyF_gAWfd54d3F1vPuZyEMfPBihmukw-aj-YbJvqcxRcZveSy2CIYs4ThVMiGTD0KrmtpDZZxrb3vZqY-LxD1agw4JQ8Ro1kH3nvPgsOOQoDQwNY5jOKemmpNcG2P2kHX_fQGXyPt2LJjH6chOSMbdN4c6meH40ZS2IwvB8txSGGFtscxJtXeDZKvpnqMDmPhCsBEquO793atjsvF-oSs6XNoHmiyF6zK6J9iITtUqXZYX6J9BKPe2OXGQkweQ", "e": "AQAB"}, {"kty": "EC", "use": "sig", "kid": "5Z3ifjhKDHwjCW1DCx2PR8NiM6n1G3p84i10Mvtv3sU", "crv": "P-256", "x": "phDWGpA1jRpPbLNncAi0g34Of_x6dASVgB0GKrskJBk", "y": "l-qt3CJm9JToAqL5jeo512K7mJn8u-RvdzE9F28SGe8"}]}
{"keys": [{"kty": "RSA", "use": "sig", "kid": "yecGJYHchQnJbz3K39V9KOyVLez8gS0H8rTCANPFumQ", "e": "AQAB", "n": "nS_gIt--OOcboxtT5SS72quz8ajGlcPW4IYrVCMaiSTKBqYRWjf0MdaRLtq1LHlwKoyu14akwfk2x3IH0Wq76NNpXyF_gAWfd54d3F1vPuZyEMfPBihmukw-aj-YbJvqcxRcZveSy2CIYs4ThVMiGTD0KrmtpDZZxrb3vZqY-LxD1agw4JQ8Ro1kH3nvPgsOOQoDQwNY5jOKemmpNcG2P2kHX_fQGXyPt2LJjH6chOSMbdN4c6meH40ZS2IwvB8txSGGFtscxJtXeDZKvpnqMDmPhCsBEquO793atjsvF-oSs6XNoHmiyF6zK6J9iITtUqXZYX6J9BKPe2OXGQkweQ"}, {"kty": "EC", "use": "sig", "kid": "5Z3ifjhKDHwjCW1DCx2PR8NiM6n1G3p84i10Mvtv3sU", "crv": "P-256", "x": "phDWGpA1jRpPbLNncAi0g34Of_x6dASVgB0GKrskJBk", "y": "l-qt3CJm9JToAqL5jeo512K7mJn8u-RvdzE9F28SGe8"}]}

View file

@ -73,20 +73,15 @@ async function beginRegistration() {
}
}
async function beginAuthentication(username) {
async function beginAuthentication() {
const statusEl = document.getElementById('webauthn-login-status');
const form = new FormData();
form.append('username', username);
try {
// Step 1: Get options from server
const beginRes = await fetch('/login/webauthn/begin', {
method: 'POST',
body: form,
});
// Step 1: Get options from server (no username needed)
const beginRes = await fetch('/login/webauthn/begin');
if (!beginRes.ok) {
const text = await beginRes.text();
if (statusEl) statusEl.innerHTML = text;
const data = await beginRes.json();
if (statusEl) statusEl.innerHTML = '<div role="alert">' + (data.error || 'Failed to start authentication') + '</div>';
return;
}
const options = await beginRes.json();
@ -100,7 +95,7 @@ async function beginAuthentication(username) {
});
}
// Step 3: Call browser WebAuthn API
// Step 3: Call browser WebAuthn API — browser shows passkey picker
const assertion = await navigator.credentials.get({ publicKey: publicKey });
// Step 4: Encode response for server
@ -126,24 +121,27 @@ async function beginAuthentication(username) {
if (completeRes.ok) {
const data = await completeRes.json();
if (data.redirect) {
window.location.href = data.redirect;
} else {
window.location.href = '/manage/credentials';
}
window.location.href = data.redirect || '/manage/credentials';
} else {
const text = await completeRes.text();
if (statusEl) statusEl.innerHTML = text;
const data = await completeRes.json().catch(function () { return {}; });
if (statusEl) statusEl.innerHTML = '<div role="alert">' + (data.error || 'Authentication failed') + '</div>';
}
} catch (err) {
if (statusEl) statusEl.innerHTML = '<div role="alert">Authentication failed: ' + err.message + '</div>';
}
}
// Wire up the registration button
// Wire up buttons
document.addEventListener('DOMContentLoaded', function () {
const registerBtn = document.getElementById('webauthn-register-btn');
if (registerBtn) {
registerBtn.addEventListener('click', beginRegistration);
}
const loginBtn = document.getElementById('webauthn-login-btn');
if (loginBtn) {
loginBtn.addEventListener('click', function () {
beginAuthentication();
});
}
});

View file

@ -24,12 +24,11 @@
<section>
<h2>Security key</h2>
<form id="webauthn-login-form">
<div>
<label for="webauthn-username">Username</label>
<input type="text" id="webauthn-username" name="username" required autocomplete="username">
</div>
<button type="button" id="webauthn-login-btn">Sign in with security key</button>
</form>
<div id="webauthn-login-status"></div>
<button type="button" id="webauthn-login-btn">Sign in with security key</button>
</section>
{% endblock %}
{% block scripts %}
<script src="/static/webauthn.js" defer></script>
{% endblock %}