En 2026, les mots de passe appartiennent définitivement au passé, et FIDO2 avec les passkeys s’imposent comme LA solution d’authentification moderne pour WordPress. Bon, je vais être honnête : quand j’ai découvert cette techno, j’étais sceptique… jusqu’à ce que je réalise qu’elle élimine complètement le phishing et transforme l’expérience utilisateur. Aujourd’hui, on va voir ensemble comment implémenter cette révolution dans vos sites WordPress, de A à Z.
Comprendre FIDO2 et WebAuthn : la révolution de l’authentification
Bon, parlons sérieusement : en 2026, on ne peut plus se contenter des mots de passe traditionnels. Avec FIDO2 et WebAuthn, on entre dans une nouvelle ère de la sécurité web. Ces technologies ne sont pas juste une mode passagère… elles représentent un vrai changement de paradigme.
Les fondements du protocole FIDO2
FIDO2 (Fast Identity Online), c’est un peu comme avoir un trousseau de clés digitales ultra-sécurisées. Développé par la FIDO Alliance, ce protocole repose sur la cryptographie asymétrique : vous avez une clé privée (qui ne quitte jamais votre appareil) et une clé publique (que le serveur connaît). Pensez à ça comme un système de cadenas : votre clé privée est la seule qui peut « déverrouiller » votre identité auprès du serveur.
Le processus fonctionne en trois étapes principales : l’enregistrement (registration), l’authentification (authentication) et l’attestation (attestation). Contrairement aux mots de passe, FIDO2 ne transmet jamais de secret partagé sur le réseau. Chaque site web reçoit une paire de clés unique… donc même si un serveur se fait pirater, vos autres comptes restent protégés.
WebAuthn API et son intégration navigateur
WebAuthn (Web Authentication API) c’est la partie visible de l’iceberg FIDO2 pour nous, développeurs web. Cette API W3C permet aux navigateurs de communiquer directement avec les authenticators : empreinte digitale, reconnaissance faciale, clés de sécurité USB, ou même votre smartphone.
Bonne nouvelle : la compatibilité navigateur est excellente en 2026. Chrome (depuis la v108), Firefox (v119+) et Safari (16+) supportent WebAuthn nativement. L’API fonctionne avec deux méthodes principales : navigator.credentials.create() pour l’enregistrement et navigator.credentials.get() pour l’authentification. Le processus challenge-response garantit que chaque tentative de connexion est unique et ne peut pas être rejouée.
Avantages sécuritaires vs mots de passe traditionnels
Alors, pourquoi FIDO2 révolutionne-t-il la sécurité ? Trois mots : phishing impossible. Avec les mots de passe traditionnels, un utilisateur peut saisir ses identifiants sur un faux site. Avec FIDO2, l’authenticator vérifie cryptographiquement l’origine du site… donc même sur un site malveillant parfaitement imité, ça ne fonctionnera pas.
Les statistiques 2026 parlent d’elles-mêmes : 78% des violations de données impliquent encore des mots de passe faibles ou réutilisés. FIDO2 élimine aussi les attaques par force brute (pas de mot de passe à deviner) et les attaques par credential stuffing. En prime, l’expérience utilisateur est bien meilleure : un simple geste biométrique remplace la saisie fastidieuse d’un mot de passe complexe.
Développer un plugin WordPress d’authentification FIDO2
Bon, maintenant qu’on a compris les bases de FIDO2, passons au concret : développer un plugin WordPress qui gère cette authentification moderne. Je vais vous montrer comment créer un plugin complet from scratch.
Structure du plugin
Pour commencer, voici l’architecture que je recommande :
wp-fido2-auth/
├── wp-fido2-auth.php (fichier principal)
├── includes/
│ ├── class-fido2-authenticator.php
│ ├── class-user-manager.php
│ └── class-admin-settings.php
├── assets/
│ ├── js/webauthn.js
│ └── css/fido2-styles.css
└── templates/
└── login-form.php
Fichier principal du plugin
Voici le code de base pour wp-fido2-auth.php :
<?php
/**
* Plugin Name: WordPress FIDO2 Authentication
* Description: Authentification sans mot de passe avec FIDO2/WebAuthn
* Version: 1.0.0
* Author: Votre Nom
*/
// Sécurité de base
if (!defined('ABSPATH')) {
exit;
}
// Constantes du plugin
define('WP_FIDO2_URL', plugin_dir_url(__FILE__));
define('WP_FIDO2_PATH', plugin_dir_path(__FILE__));
class WP_FIDO2_Auth {
public function __construct() {
add_action('init', array($this, 'init'));
add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
add_action('login_enqueue_scripts', array($this, 'enqueue_scripts'));
// Hooks d'authentification
add_filter('authenticate', array($this, 'fido2_authenticate'), 30, 3);
add_action('login_form', array($this, 'add_fido2_login_fields'));
add_action('wp_ajax_fido2_register', array($this, 'handle_registration'));
add_action('wp_ajax_fido2_authenticate', array($this, 'handle_authentication'));
}
public function init() {
// Chargement des classes
require_once WP_FIDO2_PATH . 'includes/class-fido2-authenticator.php';
require_once WP_FIDO2_PATH . 'includes/class-user-manager.php';
}
public function enqueue_scripts() {
wp_enqueue_script(
'wp-fido2-webauthn',
WP_FIDO2_URL . 'assets/js/webauthn.js',
array('jquery'),
'1.0.0',
true
);
// Localisation pour AJAX
wp_localize_script('wp-fido2-webauthn', 'fido2Ajax', array(
'ajaxurl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('fido2_nonce')
));
}
}
new WP_FIDO2_Auth();
Classe d’authentification FIDO2
Le cœur du système se trouve dans class-fido2-authenticator.php :
<?php
class FIDO2_Authenticator {
public function generate_challenge() {
return base64url_encode(random_bytes(32));
}
public function create_credential_options($user_id) {
$user = get_user_by('id', $user_id);
return array(
'challenge' => $this->generate_challenge(),
'rp' => array(
'name' => get_bloginfo('name'),
'id' => parse_url(home_url(), PHP_URL_HOST)
),
'user' => array(
'id' => base64url_encode($user->user_login),
'name' => $user->user_email,
'displayName' => $user->display_name
),
'pubKeyCredParams' => array(
array('type' => 'public-key', 'alg' => -7), // ES256
array('type' => 'public-key', 'alg' => -257) // RS256
),
'timeout' => 60000,
'attestation' => 'direct'
);
}
public function verify_credential($credential_response, $user_id) {
// Ici, vous devez implémenter la vérification
// de l'attestation et de la signature
// (utilisez une librairie comme web-auth/webauthn-lib)
$stored_credential = get_user_meta($user_id, 'fido2_credentials', true);
// Vérification basique (à compléter)
if ($this->verify_signature($credential_response, $stored_credential)) {
return true;
}
return false;
}
}
Gestion des utilisateurs
La classe class-user-manager.php s’occupe des métadonnées :
<?php
class FIDO2_User_Manager {
public function save_credential($user_id, $credential_data) {
$credentials = get_user_meta($user_id, 'fido2_credentials', true);
if (!is_array($credentials)) {
$credentials = array();
}
$credentials[] = array(
'id' => $credential_data['id'],
'public_key' => $credential_data['public_key'],
'counter' => $credential_data['counter'],
'created_at' => current_time('mysql')
);
return update_user_meta($user_id, 'fido2_credentials', $credentials);
}
public function user_has_fido2($user_id) {
$credentials = get_user_meta($user_id, 'fido2_credentials', true);
return !empty($credentials);
}
public function remove_credential($user_id, $credential_id) {
$credentials = get_user_meta($user_id, 'fido2_credentials', true);
if (is_array($credentials)) {
foreach ($credentials as $key => $cred) {
if ($cred['id'] === $credential_id) {
unset($credentials[$key]);
break;
}
}
return update_user_meta($user_id, 'fido2_credentials', array_values($credentials));
}
return false;
}
}
Interface JavaScript
Le fichier webauthn.js gère l’interaction avec l’API WebAuthn :
jQuery(document).ready(function($) {
// Enregistrement d'un nouvel authenticator
$('#fido2-register-btn').on('click', function(e) {
e.preventDefault();
// Récupération des options depuis le serveur
$.post(fido2Ajax.ajaxurl, {
action: 'fido2_get_registration_options',
nonce: fido2Ajax.nonce
}).done(function(response) {
if (response.success) {
registerCredential(response.data);
}
});
});
function registerCredential(options) {
// Conversion des données base64url
options.challenge = base64urlToArrayBuffer(options.challenge);
options.user.id = base64urlToArrayBuffer(options.user.id);
navigator.credentials.create({
publicKey: options
}).then(function(credential) {
// Envoi du credential au serveur
const credentialData = {
id: credential.id,
rawId: arrayBufferToBase64url(credential.rawId),
response: {
attestationObject: arrayBufferToBase64url(credential.response.attestationObject),
clientDataJSON: arrayBufferToBase64url(credential.response.clientDataJSON)
}
};
$.post(fido2Ajax.ajaxurl, {
action: 'fido2_register',
credential: JSON.stringify(credentialData),
nonce: fido2Ajax.nonce
}).done(function(response) {
if (response.success) {
alert('Authenticator enregistré avec succès !');
} else {
alert('Erreur lors de l\'enregistrement');
}
});
}).catch(function(error) {
console.error('Erreur WebAuthn:', error);
alert('Erreur lors de l\'enregistrement');
});
}
// Fonctions utilitaires
function base64urlToArrayBuffer(base64url) {
const base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');
const binaryString = atob(base64);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
}
function arrayBufferToBase64url(buffer) {
const bytes = new Uint8Array(buffer);
let binary = '';
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
});
Intégration avec le système d’authentification WordPress
Et voici comment intégrer tout ça avec les hooks WordPress :
public function fido2_authenticate($user, $username, $password) {
// Si l'utilisateur utilise FIDO2, on bypass le mot de passe
if (isset($_POST['fido2_response'])) {
$user_obj = get_user_by('login', $username);
if ($user_obj) {
$authenticator = new FIDO2_Authenticator();
$credential_response = json_decode(stripslashes($_POST['fido2_response']), true);
if ($authenticator->verify_credential($credential_response, $user_obj->ID)) {
return $user_obj;
} else {
return new WP_Error('fido2_failed', 'Authentification FIDO2 échouée');
}
}
}
return $user;
}
public function add_fido2_login_fields() {
$user_manager = new FIDO2_User_Manager();
echo '<div id="fido2-login-section">';
echo '<p><button type="button" id="fido2-login-btn">Se connecter avec FIDO2</button></p>';
echo '<input type="hidden" id="fido2_response" name="fido2_response" value="" />';
echo '</div>';
}
Attention cependant : ce code est une base solide, mais pour la production, vous devrez absolument utiliser une librairie spécialisée comme web-auth/webauthn-lib pour gérer la cryptographie. La vérification des signatures FIDO2, c’est du sérieux !
Implémentation pratique : enregistrement et validation des passkeys
Bon, maintenant qu’on a posé les bases avec notre plugin, passons aux choses sérieuses : l’implémentation concrète. C’est là que ça devient vraiment passionnant (et parfois un peu technique, je vous préviens !).
Processus d’enregistrement côté client et serveur
Le processus d’enregistrement, c’est le moment clé où on va créer la paire de clés cryptographiques. Côté JavaScript, on utilise navigator.credentials.create() :
const publicKeyCredentialCreationOptions = {
challenge: new Uint8Array(challenge),
rp: {
name: "Mon Site WordPress",
id: "monsite.com",
},
user: {
id: new TextEncoder().encode(userId),
name: userEmail,
displayName: userDisplayName,
},
pubKeyCredParams: [{alg: -7, type: "public-key"}],
authenticatorSelection: {
authenticatorAttachment: "platform",
userVerification: "required"
},
timeout: 60000,
attestation: "direct"
};
const credential = await navigator.credentials.create({
publicKey: publicKeyCredentialCreationOptions
});
Côté serveur PHP, on récupère et valide l’attestation avec webauthn-lib :
use Webauthn\AuthenticatorAttestationResponse;
use Webauthn\AuthenticatorAttestationResponseValidator;
public function register_passkey() {
$attestationResponse = AuthenticatorAttestationResponse::createFromArray(
json_decode(file_get_contents('php://input'), true)
);
$publicKeyCredentialSource = $this->validator->check(
$attestationResponse,
$publicKeyCredentialCreationOptions,
$request
);
// Stockage sécurisé en base
$this->store_credential($publicKeyCredentialSource, $user_id);
}
Validation et authentification des credentials
Pour l’authentification, on utilise navigator.credentials.get(). Le processus est un peu différent car on vérifie une signature existante :
const publicKeyCredentialRequestOptions = {
challenge: new Uint8Array(challenge),
allowCredentials: userCredentials.map(cred => ({
id: new Uint8Array(cred.id),
type: 'public-key',
transports: ['internal', 'hybrid']
})),
userVerification: "required",
timeout: 60000
};
const assertion = await navigator.credentials.get({
publicKey: publicKeyCredentialRequestOptions
});
La validation côté serveur utilise l’AuthenticatorAssertionResponseValidator :
use Webauthn\AuthenticatorAssertionResponse;
use Webauthn\AuthenticatorAssertionResponseValidator;
public function authenticate_passkey() {
$assertionResponse = AuthenticatorAssertionResponse::createFromArray(
json_decode(file_get_contents('php://input'), true)
);
$publicKeyCredentialSource = $this->get_user_credential(
$assertionResponse->getId()
);
$this->assertionValidator->check(
$publicKeyCredentialSource,
$assertionResponse,
$publicKeyCredentialRequestOptions,
$request,
$userHandle
);
wp_set_current_user($user_id);
wp_set_auth_cookie($user_id);
}
Gestion des erreurs et fallbacks
La gestion d’erreur, c’est crucial ! Tous les navigateurs ne supportent pas WebAuthn (même si ça s’améliore beaucoup). Voici ma stratégie de fallback :
async function attemptPasskeyAuth() {
try {
if (!window.PublicKeyCredential) {
throw new Error('WebAuthn non supporté');
}
const available = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
if (!available) {
return this.fallbackToTraditional();
}
return await this.webauthnAuth();
} catch (error) {
console.warn('Erreur WebAuthn:', error);
return this.fallbackToTraditional();
}
}
fallbackToTraditional() {
// Affichage du formulaire classique
document.getElementById('traditional-login').style.display = 'block';
// Option email de récupération
this.showEmailBackupOption();
}
J’implémente aussi des codes de secours par SMS ou email. C’est moins sécurisé mais ça évite de bloquer complètement l’utilisateur.
Interface utilisateur et expérience optimale
L’UX, c’est vraiment la clé du succès ! Un onboarding progressif fait toute la différence. Je commence par expliquer simplement :
<div class="passkey-onboarding">
<h3>Connectez-vous avec votre empreinte</h3>
<p>Plus rapide et plus sécurisé qu'un mot de passe</p>
<button class="setup-passkey">Configurer maintenant</button>
<a href="#" class="skip-for-now">Plus tard</a>
</div>
Pour les messages d’erreur, soyez explicites ! « Votre navigateur ne supporte pas cette fonctionnalité » plutôt qu’un code d’erreur cryptique. Et toujours proposer une alternative.
Attention cependant : ne forcez jamais l’utilisateur. Certains préfèrent encore les mots de passe (même si c’est moins sécurisé). L’idée, c’est d’encourager progressivement l’adoption.
Intégration avancée et écosystème WordPress
Maintenant que notre plugin FIDO2 fonctionne, on va s’attaquer au vrai défi : l’intégrer proprement avec l’écosystème WordPress existant. Parce que franchement, un site WordPress moderne sans WooCommerce ou plugins de membership, c’est rare !
Compatibilité WooCommerce et plugins tiers
L’intégration WooCommerce nécessite quelques adaptations spécifiques. Pour le checkout, on doit hooker sur woocommerce_login_form_end et woocommerce_register_form_end pour ajouter nos boutons d’authentification FIDO2. Le truc important ici : WooCommerce utilise AJAX pour ses formulaires, donc notre JavaScript doit gérer ça proprement.
Pour les comptes clients, c’est sur woocommerce_edit_account_form_end qu’on va ajouter la gestion des passkeys. Et attention : WooCommerce stocke parfois ses propres sessions, donc il faut s’assurer que notre authentification FIDO2 soit compatible avec WC_Session.
Côté plugins de membership (MemberPress, Restrict Content Pro), c’est un peu plus complexe. Chaque plugin a ses propres hooks d’authentification. Pour MemberPress par exemple, on utilise mepr-login-form et mepr_authenticate pour intégrer notre système. L’astuce : créer un système d’adaptateurs pour chaque plugin majeur.
Les systèmes LMS comme LearnDash ou LifterLMS posent des défis similaires. Il faut souvent override leurs formulaires de connexion et s’assurer que les rôles utilisateur sont correctement gérés après authentification FIDO2.
Optimisations performance et mise en cache
La performance, c’est critique avec FIDO2. D’abord, la mise en cache des credentials : on va utiliser WordPress Object Cache pour stocker temporairement les challenges générés. Ça évite les requêtes BDD répétées.
Pour le JavaScript, le lazy loading est indispensable. Notre script WebAuthn ne doit se charger que quand nécessaire : sur les pages de connexion uniquement. On peut utiliser wp_enqueue_script() avec des conditions ou implémenter un système de chargement dynamique.
Côté base de données, pensez à indexer vos tables de credentials sur user_id et credential_id. Avec des milliers d’utilisateurs, les requêtes peuvent vite devenir lentes sans index appropriés.
Le Content Security Policy (CSP) doit être configuré pour autoriser WebAuthn. Ajoutez 'unsafe-eval' uniquement si nécessaire, et préférez des nonces spécifiques pour vos scripts FIDO2.
Pour le rate limiting, implémentez des vérifications sur les tentatives d’authentification. WordPress n’a pas de système natif, mais on peut utiliser les transients pour tracker les échecs par IP. Et surtout : loggez toutes les tentatives d’authentification FIDO2 pour monitoring.
Les outils de debugging sont cruciaux : utilisez wp_debug_log() pour tracker les erreurs WebAuthn, et créez un dashboard admin pour visualiser les statistiques d’authentification. Ça aide énormément pour identifier les problèmes.
Perspectives 2026-2027 ? L’adoption va s’accélérer. WordPress Core intégrera probablement FIDO2 nativement, et les hébergeurs commenceront à proposer des optimisations spécifiques. Préparez-vous à cette transition !
