WebAuthn (Web Authentication API) is a W3C standard that enables secure passwordless authentication using public-key cryptography. Instead of passwords, users register and authenticate using hardware-based authenticators (like fingerprint readers, Face ID, YubiKeys, etc.) or platform authenticators (built-in, like Touch ID).
Find below how to handle the Registration and Authentication using a Javascript client.
How WebAuthn Registration works
User Initiates Registration:
The user provides a username and clicks Register.
Browser Requests Options from Server:
The frontend makes a POST request to get registration options.
Browser Creates Credentials:
Using the navigator.credentials.create() API via SimpleWebAuthnBrowser.startRegistration(), a credential is created.
Server Verifies Registration:
The browser sends back the credential to the server.
The server verifies the registration and stores the public key for that user.
The TsgcWSAPIServer_WebAuthn component has an html file to test the WebAuthn protocol. This HTML file contains a minimal UI and JavaScript to interact with WebAuthn.
Walkthrough of sgcWebAuthn.html
Structure Overview:
Username Input: Captures the user's identifier.
Buttons:
Register – initiates WebAuthn registration.
Authenticate – initiates login (handled similarly).
Debug Console: Shows real-time debug information (JSON from WebAuthn).
1. HTML UI for Input
<input type="text" id="username" name="username" autocomplete="username webauthn" />
<button id="btnRegBegin"><strong>Register</strong></button>
2. JavaScript: Button Click Handler
document.querySelector('#btnRegBegin').addEventListener('click', async () => {
const username = document.getElementById("username").value;
if (username == "") {
document.getElementById('Error').innerText = 'Please enter a username to register';
return;
}
const resp = await fetch('<#webauthn_registration_options>', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, algorithms: [] })
});
const options = await resp.json();
const attResp = await startRegistration(options); // WebAuthn API
3. Server Response (Fake Endpoint in HTML)
fetch('/sgcWebAuthn/Registration/Options', ...)
fetch('/sgcWebAuthn/Registration/Verify', ...)
4. Finalizing Registration
const verificationResp = await fetch('/webauthn/register/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(attResp)
});
const verificationJSON = await verificationResp.json();
if (verificationJSON && verificationJSON.verified) {
document.getElementById('Success').innerHTML = `Authenticator registered!`;
}
How WebAuthn Authentication works
User Initiates Authentication:
The user provides a username and clicks Register.
Browser Requests Options from Server:
The frontend makes a POST request to get authentication options.
Browser Creates Credentials:
Using the navigator.credentials.get() API via SimpleWebAuthnBrowser.startAuthentication().
Server Verifies Authentication:
The browser sends back the credential to the server.
The server verifies the signed result.
1. HTML UI Setup
<div class="container">
<h1>WebAuthn Authentication Sample</h1>
<section id="userdata">
<label for="username">Username:</label>
<input type="text" id="username" name="username" autocomplete="username webauthn" autofocus />
</section>
<button id="btnAuthBegin"><strong>Authenticate</strong></button>
<p id="Success" class="success"></p>
<p id="Error" class="error"></p>
<details open>
<summary>Console</summary>
<textarea id="Debug"></textarea>
</details>
</div>
2. Get Authentication Options
Before calling startAuthentication, you send the username to the server
const resp = await fetch('<#webauthn_authentication_options>', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: document.getElementById("username").value,
user_verification: 'preferred'
}),
});
The server responds with a JSON object that includes:
{
"challenge": "base64url-encoded-random-string",
"allowCredentials": [
{
"id": "base64url-credential-id",
"type": "public-key"
}
],
"userVerification": "preferred",
"rpId": "yourdomain.com"
}
This is called the PublicKeyCredentialRequestOptions.
3. Receive the Authenticator Response
The asseResp looks like this (simplified):
{
"id": "credentialId",
"rawId": "base64url-encoded-id",
"response": {
"authenticatorData": "...",
"clientDataJSON": "...",
"signature": "...",
"userHandle": "..."
},
"type": "public-key",
"clientExtensionResults": {}
}
This response proves that the user:
4. Send the Signed Authentication Response to the Server
After the user interacts with their authenticator (via startAuthentication()), you get a response object in JavaScript.
const verificationResp = await fetch('/webauthn/authenticate/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(asseResp), // this is the signed response
});
5. Get the Server's Response
The server will reply with a result like this:
{ "verified": true }
Or if something went wrong:
{ "verified": false, "error": "Invalid signature" }
And you handle it in your frontend code:
const result = await verificationResp.json();
if (result.verified) {
document.getElementById('Success').textContent = 'User authenticated!';
} else {
document.getElementById('Error').textContent = 'Authentication failed!';
}