Pile de clés métalliques sur fond rouge symbolisant le remplacement des mots de passe par les passkeys

Passkeys : pourquoi 2026 sera l’année de la fin des mots de passe (et comment les activer dès maintenant)

Introduction

Les passkeys s’imposent progressivement comme le nouveau standard d’authentification, porté par des géants comme Google, Apple et Microsoft qui poussent tous dans la même direction. Concrètement, cela signifie qu’un nombre croissant de comptes utilisateurs dans le monde peuvent déjà se connecter sans mot de passe, remplacé par une clé cryptographique stockée localement sur l’appareil. Google communique régulièrement sur l’adoption massive de cette technologie, avec un nombre de passkeys créés qui se compte en billions à l’échelle mondiale sur ses services. Si tu es développeur et que tu te demandes comment implémenter concrètement ce système dans ton application, cet article est fait pour toi : on va coder ensemble une authentification par passkeys en Python (Django et FastAPI) et en JavaScript (React et Express), avec la gestion des erreurs qu’on rencontre réellement en production.

Comprendre Passkeys

Clés cryptographiques illustrant l'authentification par passkeys sans mot de passe Crédit : MuffinLand

Une passkey, c’est une paire de clés cryptographiques (une publique, une privée) qui remplace le couple identifiant/mot de passe classique. La clé privée ne quitte jamais l’appareil de l’utilisateur (téléphone, ordinateur, clé de sécurité physique), tandis que la clé publique est stockée sur le serveur du site ou de l’application. Techniquement, cela repose sur les standards WebAuthn (porté par le W3C) et FIDO2 (porté par la FIDO Alliance), qui définissent comment le navigateur, le système d’exploitation et le serveur communiquent pour authentifier un utilisateur.

Pourquoi ce système est-il considéré comme plus sûr ? Parce qu’il élimine plusieurs risques structurels liés aux mots de passe :

  • Pas de phishing possible : la passkey est liée cryptographiquement au domaine exact du site, donc un faux site ne pourra jamais récupérer une passkey valide.
  • Pas de fuite de base de données exploitable : seule la clé publique est stockée côté serveur, elle est inutile sans la clé privée correspondante.
  • Pas de réutilisation de mot de passe : chaque passkey est unique par service, contrairement à un mot de passe qu’on a tendance à recycler.
  • Déverrouillage biométrique local : Face ID, empreinte digitale ou code PIN de l’appareil servent uniquement à débloquer la clé privée localement, ils ne sont jamais transmis au serveur.

Google explique bien ce fonctionnement sur sa page dédiée à la sécurité des passkeys, qui détaille pourquoi cette approche est considérée comme intrinsèquement plus robuste face au piratage de comptes que les mots de passe traditionnels, même renforcés par une double authentification classique.

Prérequis et installation

Avant de coder, voici ce dont tu as besoin pour suivre ce tutoriel dans de bonnes conditions.

Côté environnement technique :

  • Python 3.10 ou supérieur, avec pip et idéalement un environnement virtuel (venv)
  • Node.js version 18 ou supérieure, avec npm ou yarn
  • Un navigateur récent compatible WebAuthn : Chrome, Edge, Safari ou Firefox dans leurs versions récentes gèrent tous les passkeys nativement
  • Un appareil avec authentification biométrique ou un gestionnaire de mots de passe compatible (Google Password Manager, iCloud Keychain, ou une clé de sécurité physique type YubiKey)

Côté bibliothèques :

Pour la partie Python, on utilisera la librairie webauthn (paquet py_webauthn), compatible aussi bien avec Django qu’avec FastAPI. Installe-la ainsi :

pip install webauthn fastapi uvicorn django djangorestframework

Pour la partie JavaScript, on utilisera @simplewebauthn/server côté Express et @simplewebauthn/browser côté React :

npm install @simplewebauthn/server @simplewebauthn/browser express react

Important à savoir avant de démarrer : WebAuthn exige un contexte sécurisé, c’est-à-dire HTTPS en production (ou localhost en développement, qui est toléré nativement par les navigateurs). Sans cela, l’API navigator.credentials ne sera tout simplement pas disponible dans le navigateur.

Implémentation pas à pas

Configuration initiale

La première étape consiste à définir les paramètres de ton « Relying Party » (RP), c’est-à-dire ton serveur d’authentification. Trois informations sont essentielles :

  1. rp_id : le domaine de ton site (ex. neurautech.fr), sans protocole ni port
  2. rp_name : le nom lisible de ton application, affiché à l’utilisateur
  3. origin : l’URL complète avec protocole, utilisée pour vérifier que la requête vient bien du bon domaine

Voici la configuration côté FastAPI :

# config.py
RP_ID = "localhost"  # remplace par ton domaine en production
RP_NAME = "NeurauTech Demo"
ORIGIN = "http://localhost:8000"

Et côté Express avec SimpleWebAuthn :

// config.js
export const rpID = "localhost";
export const rpName = "NeurauTech Demo";
export const origin = `http://${rpID}:3000`;

Résultat vérifiable : à ce stade, tu dois pouvoir importer ces constantes sans erreur dans tes modules respectifs. Vérifie avec un simple print(RP_ID) ou console.log(rpID).

Code principal

Le flux d’inscription (registration) se déroule en deux temps : le serveur génère un défi (challenge), le navigateur le signe avec la clé privée de l’appareil, puis le serveur vérifie la signature.

Étape 1 — Générer les options d’enregistrement (FastAPI) :

from fastapi import FastAPI, HTTPException
from webauthn import generate_registration_options, verify_registration_response
from webauthn.helpers.structs import (
    RegistrationCredential,
    AuthenticatorSelectionCriteria,
    UserVerificationRequirement,
)
import base64

app = FastAPI()
users_db = {}  # à remplacer par une vraie base de données

@app.post("/register/begin/{username}")
def register_begin(username: str):
    user_id = base64.b64encode(username.encode()).decode()

    options = generate_registration_options(
        rp_id="localhost",
        rp_name="NeurauTech Demo",
        user_id=user_id.encode(),
        user_name=username,
        authenticator_selection=AuthenticatorSelectionCriteria(
            user_verification=UserVerificationRequirement.PREFERRED,
        ),
    )

    # On stocke le challenge temporairement pour le vérifier après
    users_db[username] = {"challenge": options.challenge, "credentials": []}

    return {
        "challenge": base64.b64encode(options.challenge).decode(),
        "rp": {"id": "localhost", "name": "NeurauTech Demo"},
        "user": {"id": user_id, "name": username, "displayName": username},
        "pubKeyCredParams": [{"alg": -7, "type": "public-key"}],
    }


@app.post("/register/complete/{username}")
def register_complete(username: str, credential: dict):
    if username not in users_db:
        raise HTTPException(status_code=400, detail="Utilisateur inconnu")

    try:
        verification = verify_registration_response(
            credential=credential,
            expected_challenge=users_db[username]["challenge"],
            expected_origin="http://localhost:8000",
            expected_rp_id="localhost",
        )
    except Exception as e:
        raise HTTPException(status_code=400, detail=f"Vérification échouée : {e}")

    users_db[username]["credentials"].append({
        "id": verification.credential_id,
        "public_key": verification.credential_public_key,
        "sign_count": verification.sign_count,
    })

    return {"status": "registered"}

Clé de sécurité USB, alternative matérielle pour l'authentification par passkeys

Étape 2 — Côté React, déclencher la création de la passkey :

import { startRegistration } from "@simplewebauthn/browser";

async function registerPasskey(username) {
  const beginResponse = await fetch(`/register/begin/${username}`, { method: "POST" });
  const options = await beginResponse.json();

  let attestationResponse;
  try {
    attestationResponse = await startRegistration(options);
  } catch (error) {
    if (error.name === "InvalidStateError") {
      console.error("Une passkey existe déjà pour cet appareil.");
    } else if (error.name === "NotAllowedError") {
      console.error("L'utilisateur a annulé ou le délai a expiré.");
    } else {
      console.error("Erreur inattendue :", error);
    }
    return;
  }

  const completeResponse = await fetch(`/register/complete/${username}`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(attestationResponse),
  });

  return completeResponse.json();
}

Cette architecture s’applique de la même façon avec Django, en remplaçant simplement les vues FastAPI par des vues Django REST Framework qui appellent les mêmes fonctions de la librairie webauthn.

Tests

Pour valider ton implémentation, procède étape par étape :

  1. Test du endpoint /register/begin : envoie une requête POST avec un nom d’utilisateur factice et vérifie que la réponse contient bien un challenge encodé en base64 et un objet user.
  2. Test dans le navigateur : ouvre les DevTools (onglet Application > WebAuthn dans Chrome), active un authentificateur virtuel, puis lance le flux d’enregistrement complet. Chrome permet de simuler une clé de sécurité sans matériel physique, ce qui est très pratique en développement.
  3. Test de la vérification serveur : provoque volontairement une erreur (challenge expiré, origin incorrect) pour t’assurer que ton except remonte bien un message clair plutôt qu’un crash silencieux.
  4. Test d’authentification (login) : une fois l’enregistrement validé, teste le flux inverse avec startAuthentication() côté client et verify_authentication_response() côté serveur.

Résultats et validation

Si tout fonctionne, tu dois observer le comportement suivant : au moment de l’inscription, le navigateur affiche une popup native proposant d’utiliser Face ID, l’empreinte digitale, un code PIN Windows Hello, ou une clé physique. Une fois validée, la passkey est stockée dans le gestionnaire de mots de passe du système (Google Password Manager, iCloud Keychain, ou équivalent) et synchronisée automatiquement sur les autres appareils liés au même compte.

Pour valider que l’authentification (login) fonctionne aussi, reproduis le même flux avec les fonctions generate_authentication_options et verify_authentication_response côté serveur, et startAuthentication() côté navigateur. Le serveur doit vérifier trois choses à chaque connexion : la signature cryptographique correspond bien à la clé publique stockée, le compteur de signatures (sign_count) a augmenté par rapport à la dernière connexion (preuve contre le clonage d’authentificateur), et l’origine de la requête correspond au domaine attendu.

Erreurs courantes en production

  • NotAllowedError : l’utilisateur a annulé la demande ou le délai (timeout) a expiré. Prévois un message clair invitant à réessayer.
  • Origin mismatch : si ton origin configuré ne correspond pas exactement à l’URL réelle (protocole, port compris), la vérification échoue systématiquement.
  • Comptes multi-appareils : un utilisateur peut créer plusieurs passkeys (téléphone, ordinateur portable, clé physique). Ton schéma de base de données doit stocker une liste de credentials par utilisateur, pas un seul.
  • Fallback nécessaire : tous les utilisateurs n’ont pas encore un appareil compatible. Prévoir un mécanisme de secours (email magique, TOTP) reste recommandé pendant la période de transition.

Tableau comparatif : passkeys vs mots de passe vs 2FA/TOTP

Critère Mot de passe seul Mot de passe + 2FA/TOTP Passkeys
Résistance au phishing Faible Moyenne Élevée
Résistance aux fuites de base de données Faible Moyenne Élevée (clé publique inutile seule)
Facilité d’usage utilisateur Moyenne (à mémoriser) Faible (étape supplémentaire) Élevée (biométrie locale)
Compatibilité navigateurs Universelle Universelle Chrome, Edge, Safari, Firefox récents
Compatibilité OS Universelle Universelle Windows 10/11, macOS récent, iOS/iPadOS récent, Android récent
Synchronisation multi-appareils Manuelle Manuelle Automatique via cloud (Google, Apple)
Complexité d’implémentation Faible Moyenne Moyenne à élevée
Risque d’oubli utilisateur Élevé Élevé Faible (lié à l’appareil)

FAQ

Pile de clés métalliques sur fond rouge symbolisant le remplacement des mots de passe par les passkeys

Qu’est-ce que passkeys et à quoi ça sert ?

Les passkeys sont une méthode d’authentification qui remplace le mot de passe par une paire de clés cryptographiques générées et stockées sur l’appareil de l’utilisateur. Concrètement, cela sert à se connecter à un site ou une application sans avoir à taper ni mémoriser de mot de passe : il suffit de déverrouiller son téléphone ou son ordinateur avec son empreinte, son visage ou son code PIN. L’objectif principal est de supprimer les risques liés au phishing et aux fuites de bases de données, deux causes majeures de piratage de comptes aujourd’hui.

Comment fonctionne passkeys concrètement ?

Lors de l’inscription, le navigateur génère localement une paire de clés : la clé privée reste sur l’appareil (protégée par la puce sécurisée du téléphone ou de l’ordinateur), tandis que la clé publique est envoyée et stockée sur le serveur. À chaque connexion, le serveur envoie un défi aléatoire (challenge) que l’appareil signe avec la clé privée après déverrouillage biométrique. Le serveur vérifie ensuite cette signature avec la clé publique déjà enregistrée. Si la signature correspond, l’utilisateur est authentifié, sans qu’aucun secret n’ait jamais transité sur le réseau.

Quels sont les prérequis pour utiliser passkeys ?

Il faut un appareil compatible (smartphone récent, ordinateur avec authentification biométrique ou clé de sécurité physique), un navigateur supportant WebAuthn (Chrome, Safari, Edge ou Firefox dans leurs versions récentes), et un compte utilisateur lié à un gestionnaire de passkeys comme Google Password Manager ou iCloud Keychain. Côté développeur, il faut en plus un serveur exposé en HTTPS (sauf en local) et une librairie compatible WebAuthn/FIDO2 comme py_webauthn en Python ou SimpleWebAuthn en JavaScript.

Comment mot de passe et comment de passe fonctionnent-ils encore aux côtés des passkeys ?

Le mot de passe classique reste un mécanisme de secours utile pendant la transition, notamment pour les utilisateurs dont l’appareil n’est pas encore compatible avec les passkeys ou qui n’ont pas activé cette fonctionnalité. La plupart des grandes plateformes proposent aujourd’hui les deux options en parallèle : l’utilisateur peut choisir de continuer avec son mot de passe habituel, éventuellement renforcé par une double authentification (2FA/TOTP), ou basculer vers une passkey pour une expérience plus rapide et plus sûre, sans avoir à retenir de combinaison complexe.

Comment are passkeys plus sûres qu’un mot de passe traditionnel ?

Les passkeys sont considérées plus sûres car elles suppriment le secret partagé sur le réseau : contrairement à un mot de passe qui peut être intercepté, deviné ou volé via une fausse page de connexion, la clé privée d’une passkey ne quitte jamais l’appareil de l’utilisateur. De plus, chaque passkey est cryptographiquement liée au domaine exact du site pour lequel elle a été créée, ce qui rend le phishing quasiment inopérant, même face à un faux site visuellement identique à l’original.

Sources et références

Conclusion

Les passkeys ne sont plus une technologie expérimentale réservée aux early adopters : elles sont déjà intégrées nativement dans les principaux navigateurs et systèmes d’exploitation, et de plus en plus de services en ligne les proposent comme méthode de connexion par défaut. Pour un développeur, l’enjeu n’est plus de savoir s’il faut les adopter, mais quand et comment les intégrer proprement dans son application existante, en gérant intelligemment la coexistence avec les mots de passe classiques pendant la période de transition. Le code présenté dans cet article te donne une base solide pour démarrer : à toi maintenant de l’adapter à ton architecture, de soigner la gestion des erreurs, et de tester rigoureusement chaque flux avant de déployer en production.


Écrit par Abdelilah, fondateur de NeurauTech — un blog qui vulgarise l’IA, le cloud, la cybersécurité et la tech pour un large public. Article régulièrement mis à jour.

Publications similaires