Présentation
Voici un aperçu général des étapes clés de l'authentification par clé d'accès:
- Définissez la question d'authentification et les autres options nécessaires pour l'authentification avec une clé d'accès. Envoyez-les au client afin de pouvoir les transmettre à votre appel d'authentification par clé d'accès (
navigator.credentials.get
sur le Web). Une fois que l'utilisateur a confirmé l'authentification par clé d'accès, l'appel d'authentification par clé d'accès est résolu et renvoie un identifiant (PublicKeyCredential
). L'identifiant contient une assertion d'authentification.
- Vérifiez l'assertion d'authentification.
- Si l'assertion d'authentification est valide, authentifiez l'utilisateur.
Les sections suivantes présentent les spécificités de chaque étape.
<ph type="x-smartling-placeholder">Créer le défi
En pratique, un défi est un tableau d'octets aléatoires, représenté sous la forme d'un objet ArrayBuffer
.
// Example challenge, base64URL-encoded
weMLPOSx1VfSnMV6uPwDKbjGdKRMaUDGxeDEUTT5VN8
Pour vous assurer que le défi remplit son objectif, vous devez:
- Assurez-vous que le même test ne doit jamais être utilisé plusieurs fois. Générez un nouveau test à chaque tentative de connexion. Annulez le défi après chaque tentative de connexion, qu'elle ait réussi ou échoué. Vous pouvez aussi supprimer le défi après un certain temps. N'acceptez jamais plus d'une fois la même question d'authentification dans une réponse.
- Assurez-vous que le test est sécurisé de manière cryptographique. Un défi doit être pratiquement impossible à deviner. Pour créer un test sécurisé de manière cryptographique côté serveur, il est préférable de vous fier à une bibliothèque côté serveur FIDO de confiance. Si vous créez vos propres tests, utilisez la fonctionnalité cryptographique intégrée disponible dans votre pile technologique ou recherchez des bibliothèques conçues pour les cas d'utilisation de cryptographie. Exemples : iso-crypto en Node.js ou secrets en Python. Conformément à la spécification, la question d'authentification doit comporter au moins 16 octets pour être considérée comme sécurisée.
Une fois que vous avez créé un test, enregistrez-le dans la session de l'utilisateur pour le vérifier plus tard.
Créer des options de demande d'identifiants
Créez des options de demande d'identifiants en tant qu'objet publicKeyCredentialRequestOptions
.
Pour ce faire, utilisez votre bibliothèque FIDO côté serveur. Il propose généralement une fonction utilitaire capable de créer ces options à votre place. SimpleWebAuthn propose, par exemple, generateAuthenticationOptions
.
publicKeyCredentialRequestOptions
doit contenir toutes les informations nécessaires à l'authentification par clé d'accès. Transmettez ces informations à la fonction de votre bibliothèque FIDO côté serveur chargée de créer l'objet publicKeyCredentialRequestOptions
.
Certaines de publicKeyCredentialRequestOptions
peuvent être des constantes. D'autres doivent être définies de manière dynamique sur le serveur:
rpId
: ID de tiers assujetti à des restrictions auquel l'identifiant doit être associé (par exemple,example.com
). L'authentification ne réussira que si l'ID de RP que vous fournissez ici correspond à l'ID de RP associé au justificatif. Pour renseigner l'ID de RP, utilisez la même valeur que l'ID de RP que vous avez défini danspublicKeyCredentialCreationOptions
lors de l'enregistrement des identifiants.challenge
: donnée que le fournisseur de clé d'accès signe pour prouver que l'utilisateur détient la clé d'accès au moment de la requête d'authentification. Consultez les détails dans Créer le défi.allowCredentials
: tableau des identifiants acceptés pour cette authentification. Transmettez un tableau vide pour permettre à l'utilisateur de sélectionner une clé d'accès disponible dans une liste affichée par le navigateur. Pour en savoir plus, consultez Récupérer une question d'authentification sur le serveur de tiers assujettis à des restrictions et Informations détaillées sur les identifiants détectables.userVerification
: indique si la validation de l'utilisateur à l'aide du verrouillage de l'écran de l'appareil est "obligatoire" ou "recommandée" ou "déconseillé". Consultez Récupérer une question d'authentification sur le serveur du tiers assujetti à des restrictions.timeout
: délai (en millisecondes) requis par l'utilisateur pour procéder à l'authentification. Il doit être raisonnablement généreux et plus court que la durée de vie de l'challenge
. La valeur par défaut recommandée est 5 minutes, mais vous pouvez l'augmenter. Jusqu'à 10 minutes, ce qui reste dans la plage recommandée. Des délais avant expiration longs sont utiles si vous prévoyez que les utilisateurs utilisent le workflow hybride, qui prend généralement un peu plus de temps. Si l'opération expire, une erreurNotAllowedError
est générée.
Une fois que vous avez créé publicKeyCredentialRequestOptions
, envoyez-le au client.
Exemple de code: créer des options de demande d'identifiants
Nous utilisons la bibliothèque SimpleWebAuthn dans nos exemples. Ici, nous transférons la création d'options de demande d'identifiants à sa fonction generateAuthenticationOptions
.
import {
generateRegistrationOptions,
verifyRegistrationResponse,
generateAuthenticationOptions,
verifyAuthenticationResponse
} from '@simplewebauthn/server';
router.post('/signinRequest', csrfCheck, async (req, res) => {
// Ensure you nest calls in try/catch blocks.
// If something fails, throw an error with a descriptive error message.
// Return that message with an appropriate error code to the client.
try {
// Use the generateAuthenticationOptions function from SimpleWebAuthn
const options = await generateAuthenticationOptions({
rpID: process.env.HOSTNAME,
allowCredentials: [],
});
// Save the challenge in the user session
req.session.challenge = options.challenge;
return res.json(options);
} catch (e) {
console.error(e);
return res.status(400).json({ error: e.message });
}
});
Valider et connecter l'utilisateur
Lorsque navigator.credentials.get
est résolu correctement sur le client, il renvoie un objet PublicKeyCredential
.
Le response
est un AuthenticatorAssertionResponse
. Il représente la réponse du fournisseur de clé d'accès à l'instruction du client pour créer les éléments nécessaires pour essayer de s'authentifier avec une clé d'accès sur la RP. Il comprend :
response.authenticatorData
etresponse.clientDataJSON
, comme lors de l'enregistrement d'une clé d'accès.response.signature
qui contient une signature sur ces valeurs.
Envoyez l'objet PublicKeyCredential
au serveur.
Sur le serveur, procédez comme suit:
<ph type="x-smartling-placeholder">- Réunissez les informations dont vous aurez besoin pour vérifier l'assertion et authentifier l'utilisateur:
<ph type="x-smartling-placeholder">
- </ph>
- Récupérez la question d'authentification attendue stockée dans la session lorsque vous avez généré les options d'authentification.
- Obtenez l'origine et l'ID de RP attendus.
- Identifiez l'utilisateur dans votre base de données. Dans le cas des identifiants détectables, vous ne savez pas qui est l'utilisateur à l'origine de la demande d'authentification. Pour le savoir, deux possibilités s'offrent à vous:
<ph type="x-smartling-placeholder">
- </ph>
- Option 1: Utilisez
response.userHandle
dans l'objetPublicKeyCredential
. Dans la table Utilisateurs, recherchez lepasskey_user_id
qui correspond àuserHandle
. - Option 2: Utilisez l'identifiant
id
présent dans l'objetPublicKeyCredential
. Dans le tableau Identifiants de clé publique, recherchez l'identifiantid
qui correspond à l'identifiantid
présent dans l'objetPublicKeyCredential
. Recherchez ensuite l'utilisateur correspondant à l'aide de la clé étrangèrepasskey_user_id
dans votre table Users.
- Option 1: Utilisez
- Recherchez dans votre base de données les informations d'identification de clé publique correspondant à l'assertion d'authentification que vous avez reçue. Pour ce faire, dans le tableau Identifiants de clé publique, recherchez l'identifiant
id
qui correspond à l'identifiantid
présent dans l'objetPublicKeyCredential
.
Vérifiez l'assertion d'authentification. Transférez cette étape de validation à votre bibliothèque FIDO côté serveur, qui propose généralement une fonction utilitaire permettant d'atteindre cet objectif. SimpleWebAuthn propose, par exemple,
verifyAuthenticationResponse
. Pour en savoir plus, consultez l'annexe: Vérification de la réponse d'authentification.Supprimez la question d'authentification, qu'elle aboutisse ou non, pour éviter les attaques par rejeu.
Connectez l'utilisateur. Si la validation a réussi, mettez à jour les informations de session pour indiquer que l'utilisateur est connecté. Vous pouvez également renvoyer un objet
user
au client, afin que le frontend puisse utiliser les informations associées à l'utilisateur nouvellement connecté.
Exemple de code: valider l'utilisateur et se connecter
Nous utilisons la bibliothèque SimpleWebAuthn dans nos exemples. Ici, nous transférons la vérification de la réponse d'authentification à sa fonction verifyAuthenticationResponse
.
import {
generateRegistrationOptions,
verifyRegistrationResponse,
generateAuthenticationOptions,
verifyAuthenticationResponse
} from '@simplewebauthn/server';
import { isoBase64URL } from '@simplewebauthn/server/helpers';
router.post('/signinResponse', csrfCheck, async (req, res) => {
const response = req.body;
const expectedChallenge = req.session.challenge;
const expectedOrigin = getOrigin(req.get('User-Agent'));
const expectedRPID = process.env.HOSTNAME;
// Ensure you nest verification function calls in try/catch blocks.
// If something fails, throw an error with a descriptive error message.
// Return that message with an appropriate error code to the client.
try {
// Find the credential stored to the database by the credential ID
const cred = Credentials.findById(response.id);
if (!cred) {
throw new Error('Credential not found.');
}
// Find the user - Here alternatively we could look up the user directly
// in the Users table via userHandle
const user = Users.findByPasskeyUserId(cred.passkey_user_id);
if (!user) {
throw new Error('User not found.');
}
// Base64URL decode some values
const authenticator = {
credentialPublicKey: isoBase64URL.toBuffer(cred.publicKey),
credentialID: isoBase64URL.toBuffer(cred.id),
transports: cred.transports,
};
// Verify the credential
const { verified, authenticationInfo } = await verifyAuthenticationResponse({
response,
expectedChallenge,
expectedOrigin,
expectedRPID,
authenticator,
requireUserVerification: false,
});
if (!verified) {
throw new Error('User verification failed.');
}
// Kill the challenge for this session.
delete req.session.challenge;
req.session.username = user.username;
req.session['signed-in'] = 'yes';
return res.json(user);
} catch (e) {
delete req.session.challenge;
console.error(e);
return res.status(400).json({ error: e.message });
}
});
Annexe: Vérification de la réponse d'authentification
La vérification de la réponse d'authentification comprend les vérifications suivantes:
- Assurez-vous que l'ID du tiers assujetti à des restrictions correspond à votre site.
- Assurez-vous que l'origine de la requête correspond à celle de la connexion à votre site. Pour les applications Android, consultez Valider l'origine.
- Vérifiez que l'appareil a pu fournir la question d'authentification que vous lui avez posée.
- Vérifiez que lors de l'authentification, l'utilisateur a respecté les exigences que vous exigez en tant que tiers assujetti à des restrictions. Si vous exigez la validation de l'utilisateur, assurez-vous que l'indicateur
uv
(validé par l'utilisateur) dansauthenticatorData
est défini surtrue
. Vérifiez que l'optionup
(utilisateur présent) dansauthenticatorData
esttrue
, car la présence de l'utilisateur est toujours requise pour les clés d'accès. - Vérifiez la signature. Pour valider la signature, vous avez besoin des éléments suivants:
<ph type="x-smartling-placeholder">
- </ph>
- La signature, qui correspond au défi signé:
response.signature
- Clé publique avec laquelle vérifier la signature.
- Données signées d'origine. Il s'agit des données dont la signature doit être vérifiée.
- Algorithme cryptographique utilisé pour créer la signature.
- La signature, qui correspond au défi signé:
Pour en savoir plus sur ces étapes, consultez le code source pour verifyAuthenticationResponse
de SimpleWebAuthn ou consultez la liste complète des vérifications dans les spécifications.