WebAuthn (Web Authentication API) est un standard W3C qui permet une authentification sans mot de passe sécurisée grâce à la cryptographie à clé publique. Au lieu des mots de passe, les utilisateurs s'enregistrent et s'authentifient à l'aide d'authentificateurs matériels (comme les lecteurs d'empreintes digitales, Face ID, YubiKeys, etc.) ou d'authentificateurs de plateforme intégrés (comme Touch ID).
Voici comment gérer l'enregistrement et l'authentification à l'aide d'un client Javascript.
Fonctionnement de l'enregistrement WebAuthn
L'utilisateur lance l'enregistrement :
L'utilisateur fournit un nom d'utilisateur et clique sur Register.
Le navigateur demande des options au serveur :
Le frontend effectue une requête POST pour obtenir les options d'inscription.
Le navigateur crée des identifiants :
En utilisant l'API navigator.credentials.create() via SimpleWebAuthnBrowser.startRegistration(), un credential est créé.
Le serveur vérifie l'enregistrement :
Le navigateur renvoie la credential au serveur.
Le serveur vérifie l'enregistrement et stocke la clé publique pour cet utilisateur.
Le composant TsgcWSAPIServer_WebAuthn dispose d'un fichier HTML pour tester le protocole WebAuthn. Ce fichier HTML contient une interface minimale et du code JavaScript pour interagir avec WebAuthn.
Présentation de sgcWebAuthn.html
Vue d'ensemble de la structure :
Saisie du nom d'utilisateur : Capture l'identifiant de l'utilisateur.
Boutons :
Register – initie l'enregistrement WebAuthn.
Authenticate – initie la connexion (gérée de manière similaire).
Console de débogage : Affiche des informations de débogage en temps réel (JSON provenant de WebAuthn).
1. Interface HTML pour la saisie
<input type="text" id="username" name="username" autocomplete="username webauthn" />
<button id="btnRegBegin"><strong>Register</strong></button>
2. JavaScript : Gestionnaire de clic sur le bouton
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. Réponse du serveur (point de terminaison fictif en HTML)
fetch('/sgcWebAuthn/Registration/Options', ...)
fetch('/sgcWebAuthn/Registration/Verify', ...)
4. Finalisation de l'enregistrement
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!`;
}
Fonctionnement de l'authentification WebAuthn
L'utilisateur initie l'authentification :
L'utilisateur fournit un nom d'utilisateur et clique sur Register.
Le navigateur demande des options au serveur :
Le frontend fait une requête POST pour obtenir les options d'authentification.
Le navigateur crée des identifiants :
Utilisation de l'API navigator.credentials.get() via SimpleWebAuthnBrowser.startAuthentication().
Le serveur vérifie l'authentification :
Le navigateur renvoie le credential au serveur.
Le serveur vérifie le résultat signé.
1. Configuration de l'interface HTML
<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. Obtenir les options d'authentification
Avant d'appeler startAuthentication, vous envoyez le nom d'utilisateur au serveur
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'
}),
});
Le serveur répond avec un objet JSON qui inclut :
{
"challenge": "base64url-encoded-random-string",
"allowCredentials": [
{
"id": "base64url-credential-id",
"type": "public-key"
}
],
"userVerification": "preferred",
"rpId": "yourdomain.com"
}
C'est ce qu'on appelle PublicKeyCredentialRequestOptions.
3. Recevoir la réponse de l'authentificateur
La réponse asseResp ressemble à ceci (simplifiée) :
{
"id": "credentialId",
"rawId": "base64url-encoded-id",
"response": {
"authenticatorData": "...",
"clientDataJSON": "...",
"signature": "...",
"userHandle": "..."
},
"type": "public-key",
"clientExtensionResults": {}
}
Cette réponse prouve que l'utilisateur :
4. Envoyer la réponse d'authentification signée au serveur
Après que l'utilisateur interagit avec son authentificateur (via startAuthentication()), vous obtenez un objet de réponse en 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. Obtenir la réponse du serveur
Le serveur répondra avec un résultat de ce type :
{ "verified": true }
Ou si quelque chose s'est mal passé :
{ "verified": false, "error": "Invalid signature" }
Et vous le gérez dans votre code frontend :
const result = await verificationResp.json();
if (result.verified) {
document.getElementById('Success').textContent = 'User authenticated!';
} else {
document.getElementById('Error').textContent = 'Authentication failed!';
}