<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Neurau Tech</title>
    <description>The latest articles on DEV Community by Neurau Tech (@neurau_tech).</description>
    <link>https://dev.to/neurau_tech</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3994593%2F49cb77af-0363-49eb-af59-7b2c101fad24.png</url>
      <title>DEV Community: Neurau Tech</title>
      <link>https://dev.to/neurau_tech</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/neurau_tech"/>
    <language>en</language>
    <item>
      <title>Passkeys : pourquoi 2026 sera l'année de la fin des mots de passe (et comment les activer dès maintenant)</title>
      <dc:creator>Neurau Tech</dc:creator>
      <pubDate>Fri, 31 Jul 2026 09:35:10 +0000</pubDate>
      <link>https://dev.to/neurau_tech/passkeys-pourquoi-2026-sera-lannee-de-la-fin-des-mots-de-passe-et-comment-les-activer-des-4g3k</link>
      <guid>https://dev.to/neurau_tech/passkeys-pourquoi-2026-sera-lannee-de-la-fin-des-mots-de-passe-et-comment-les-activer-des-4g3k</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Cet article a été originalement publié sur &lt;a href="https://neurautech.com/passkeys/" rel="noopener noreferrer"&gt;NeurauTech&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comprendre Passkeys
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fenwyphlyrcn6vmmiwilk.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fenwyphlyrcn6vmmiwilk.jpeg" alt="A close-up view of keys with a heart keychain hanging on a branch, captured in a Paris garden. — passkeys" width="799" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Crédit : MuffinLand&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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 :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pas de phishing possible&lt;/strong&gt; : la passkey est liée cryptographiquement au domaine exact du site, donc un faux site ne pourra jamais récupérer une passkey valide.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pas de fuite de base de données exploitable&lt;/strong&gt; : seule la clé publique est stockée côté serveur, elle est inutile sans la clé privée correspondante.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pas de réutilisation de mot de passe&lt;/strong&gt; : chaque passkey est unique par service, contrairement à un mot de passe qu'on a tendance à recycler.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Déverrouillage biométrique local&lt;/strong&gt; : 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.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;
  
  
  Prérequis et installation
&lt;/h2&gt;

&lt;p&gt;Avant de coder, voici ce dont tu as besoin pour suivre ce tutoriel dans de bonnes conditions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Côté environnement technique :&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.10 ou supérieur, avec &lt;code&gt;pip&lt;/code&gt; et idéalement un environnement virtuel (&lt;code&gt;venv&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Node.js version 18 ou supérieure, avec &lt;code&gt;npm&lt;/code&gt; ou &lt;code&gt;yarn&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Un navigateur récent compatible WebAuthn : Chrome, Edge, Safari ou Firefox dans leurs versions récentes gèrent tous les passkeys nativement&lt;/li&gt;
&lt;li&gt;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)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Côté bibliothèques :&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pour la partie Python, on utilisera la librairie &lt;code&gt;webauthn&lt;/code&gt; (paquet &lt;code&gt;py_webauthn&lt;/code&gt;), compatible aussi bien avec Django qu'avec FastAPI. Installe-la ainsi :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;webauthn fastapi uvicorn django djangorestframework
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pour la partie JavaScript, on utilisera &lt;code&gt;@simplewebauthn/server&lt;/code&gt; côté Express et &lt;code&gt;@simplewebauthn/browser&lt;/code&gt; côté React :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @simplewebauthn/server @simplewebauthn/browser express react
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  Implémentation pas à pas
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Configuration initiale
&lt;/h3&gt;

&lt;p&gt;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 :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;rp_id&lt;/strong&gt; : le domaine de ton site (ex. &lt;code&gt;neurautech.fr&lt;/code&gt;), sans protocole ni port&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;rp_name&lt;/strong&gt; : le nom lisible de ton application, affiché à l'utilisateur&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;origin&lt;/strong&gt; : l'URL complète avec protocole, utilisée pour vérifier que la requête vient bien du bon domaine&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Voici la configuration côté FastAPI :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config.py
&lt;/span&gt;&lt;span class="n"&gt;RP_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# remplace par ton domaine en production
&lt;/span&gt;&lt;span class="n"&gt;RP_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NeurauTech Demo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;ORIGIN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Et côté Express avec SimpleWebAuthn :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rpID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;localhost&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rpName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NeurauTech Demo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;origin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`http://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rpID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:3000`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  Code principal
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Étape 1 — Générer les options d'enregistrement (FastAPI) :&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTPException&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;webauthn&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;generate_registration_options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verify_registration_response&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;webauthn.helpers.structs&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;RegistrationCredential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;AuthenticatorSelectionCriteria&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;UserVerificationRequirement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;users_db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;  &lt;span class="c1"&gt;# à remplacer par une vraie base de données
&lt;/span&gt;
&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/register/begin/{username}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;register_begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_registration_options&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;rp_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;rp_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NeurauTech Demo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;user_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;authenticator_selection&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;AuthenticatorSelectionCriteria&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;user_verification&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;UserVerificationRequirement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PREFERRED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# On stocke le challenge temporairement pour le vérifier après
&lt;/span&gt;    &lt;span class="n"&gt;users_db&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;challenge&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;credentials&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;challenge&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NeurauTech Demo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;displayName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pubKeyCredParams&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;public-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/register/complete/{username}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;register_complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;users_db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Utilisateur inconnu&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;verification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;verify_registration_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;expected_challenge&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;users_db&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;challenge&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;expected_origin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;expected_rp_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Vérification échouée : &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Hand&lt;/span&gt; &lt;span class="n"&gt;holding&lt;/span&gt; &lt;span class="n"&gt;keychain&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;integrated&lt;/span&gt; &lt;span class="n"&gt;USB&lt;/span&gt; &lt;span class="n"&gt;flash&lt;/span&gt; &lt;span class="n"&gt;drive&lt;/span&gt; &lt;span class="n"&gt;against&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;plain&lt;/span&gt; &lt;span class="n"&gt;background&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="n"&gt;passkeys&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pexels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;photos&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;5474291&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pexels&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;photo&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;5474291.j&lt;/span&gt;&lt;span class="n"&gt;peg&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="n"&gt;auto&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;compress&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tinysrgb&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;dpr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;650&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;940&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="n"&gt;users_db&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;credentials&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;verification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;credential_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;public_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;verification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;credential_public_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sign_count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;verification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sign_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;registered&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Étape 2 — Côté React, déclencher la création de la passkey :&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;startRegistration&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@simplewebauthn/browser&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;registerPasskey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;beginResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/register/begin/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;beginResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;attestationResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;attestationResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;startRegistration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;InvalidStateError&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Une passkey existe déjà pour cet appareil.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NotAllowedError&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;L'utilisateur a annulé ou le délai a expiré.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Erreur inattendue :&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;completeResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/register/complete/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attestationResponse&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;completeResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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 &lt;code&gt;webauthn&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tests
&lt;/h3&gt;

&lt;p&gt;Pour valider ton implémentation, procède étape par étape :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Test du endpoint &lt;code&gt;/register/begin&lt;/code&gt;&lt;/strong&gt; : envoie une requête POST avec un nom d'utilisateur factice et vérifie que la réponse contient bien un &lt;code&gt;challenge&lt;/code&gt; encodé en base64 et un objet &lt;code&gt;user&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test dans le navigateur&lt;/strong&gt; : ouvre les DevTools (onglet Application &amp;gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test de la vérification serveur&lt;/strong&gt; : provoque volontairement une erreur (challenge expiré, origin incorrect) pour t'assurer que ton &lt;code&gt;except&lt;/code&gt; remonte bien un message clair plutôt qu'un crash silencieux.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test d'authentification (login)&lt;/strong&gt; : une fois l'enregistrement validé, teste le flux inverse avec &lt;code&gt;startAuthentication()&lt;/code&gt; côté client et &lt;code&gt;verify_authentication_response()&lt;/code&gt; côté serveur.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Résultats et validation
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Pour valider que l'authentification (login) fonctionne aussi, reproduis le même flux avec les fonctions &lt;code&gt;generate_authentication_options&lt;/code&gt; et &lt;code&gt;verify_authentication_response&lt;/code&gt; côté serveur, et &lt;code&gt;startAuthentication()&lt;/code&gt; 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 (&lt;code&gt;sign_count&lt;/code&gt;) 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Erreurs courantes en production
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;NotAllowedError&lt;/code&gt;&lt;/strong&gt; : l'utilisateur a annulé la demande ou le délai (timeout) a expiré. Prévois un message clair invitant à réessayer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Origin mismatch&lt;/strong&gt; : si ton &lt;code&gt;origin&lt;/code&gt; configuré ne correspond pas exactement à l'URL réelle (protocole, port compris), la vérification échoue systématiquement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comptes multi-appareils&lt;/strong&gt; : 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fallback nécessaire&lt;/strong&gt; : 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.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tableau comparatif : passkeys vs mots de passe vs 2FA/TOTP
&lt;/h2&gt;

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

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffj65z1e7ylwcaraqwi0i.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffj65z1e7ylwcaraqwi0i.jpeg" alt="Detailed view of a pile of metallic keys on a deep red surface, emphasizing security and safety. — passkeys" width="799" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Qu'est-ce que passkeys et à quoi ça sert ?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Comment fonctionne passkeys concrètement ?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quels sont les prérequis pour utiliser passkeys ?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;py_webauthn&lt;/code&gt; en Python ou &lt;code&gt;SimpleWebAuthn&lt;/code&gt; en JavaScript.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Comment mot de passe et comment de passe fonctionnent-ils encore aux côtés des passkeys ?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Comment are passkeys plus sûres qu'un mot de passe traditionnel ?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources et références
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://safety.google/intl/en/safety/authentication/passkey/" rel="noopener noreferrer"&gt;Google Safety — Comprendre les passkeys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.google.com/account/about/passkeys/" rel="noopener noreferrer"&gt;Google Account — À propos des passkeys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/identity/passkeys" rel="noopener noreferrer"&gt;Google Developers — Documentation technique Passkeys&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Écrit par **Abdelilah&lt;/em&gt;&lt;em&gt;, fondateur de &lt;a href="https://neurautech.com" rel="noopener noreferrer"&gt;NeurauTech&lt;/a&gt; — un blog qui vulgarise l'IA, le cloud, la cybersécurité et la tech pour un large public. Article régulièrement mis à jour.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Vous avez aimé cet article ?&lt;/strong&gt; Retrouvez plus de contenu technique sur &lt;a href="https://neurautech.com" rel="noopener noreferrer"&gt;NeurauTech&lt;/a&gt; — IA, Python, LLM, et systèmes multi-agents.&lt;/p&gt;

</description>
      <category>passkeys</category>
      <category>cyberscurit</category>
      <category>motdepasse</category>
      <category>arepasskeys</category>
    </item>
    <item>
      <title>Portfolio développeur : comment le construire pour trouver des clients (2026)</title>
      <dc:creator>Neurau Tech</dc:creator>
      <pubDate>Sat, 25 Jul 2026 13:25:28 +0000</pubDate>
      <link>https://dev.to/neurau_tech/portfolio-developpeur-comment-le-construire-pour-trouver-des-clients-2026-3nho</link>
      <guid>https://dev.to/neurau_tech/portfolio-developpeur-comment-le-construire-pour-trouver-des-clients-2026-3nho</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhwc3am30uremq5yfsidz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhwc3am30uremq5yfsidz.png" alt="portfolio developpeur" width="799" height="436"&gt;&lt;/a&gt;&lt;br&gt;
Créer un portfolio développeur qui attire des clients en 2026 (guide complet)&lt;br&gt;
&lt;a href="https://neurautech.com/portfolio-developpeur-comment-le-construire-2026/" rel="noopener noreferrer"&gt;l'article original&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ton portfolio est souvent la première chose qu'un recruteur ou un client potentiel regarde. Avant ton CV, avant LinkedIn, avant ta lettre de motivation. Et en 10 secondes, il décide si tu mérites plus d'attention.&lt;/p&gt;

&lt;p&gt;Un mauvais portfolio = candidature jetée. Un bon portfolio = appel téléphonique.&lt;/p&gt;

&lt;p&gt;Ce guide te montre exactement quoi mettre dedans pour que ton portfolio travaille pour toi.&lt;/p&gt;

&lt;p&gt;Pourquoi votre portfolio est votre meilleure carte de visite&lt;/p&gt;

&lt;p&gt;Les certifications et diplômes indiquent que tu as suivi une formation. Le portfolio prouve que tu sais vraiment coder.&lt;/p&gt;

&lt;p&gt;En 2026, avec des centaines de bootcamps et de formations en ligne, les recruteurs et clients voient des profils "développeur" qui ne savent pas coder. Ton portfolio fait la différence.&lt;/p&gt;

&lt;p&gt;Et contrairement à un CV qui ne peut que raconter, un portfolio peut montrer — des projets live, du code sur GitHub, des résultats concrets.&lt;/p&gt;

&lt;p&gt;Les éléments indispensables d'un bon portfolio&lt;/p&gt;

&lt;p&gt;Section Hero (page d'accueil)&lt;/p&gt;

&lt;p&gt;En 5 secondes, le visiteur doit comprendre : qui tu es, ce que tu fais, et comment te contacter.&lt;/p&gt;

&lt;p&gt;Exemple : "Je suis Thomas, développeur React/Node.js senior. Je construis des applications web performantes pour des startups et PME. Disponible pour des missions freelance."&lt;/p&gt;

&lt;p&gt;Projets (la section la plus importante)&lt;/p&gt;

&lt;p&gt;3 à 5 projets bien présentés valent mieux que 15 projets bâclés.&lt;/p&gt;

&lt;p&gt;Pour chaque projet, inclus :&lt;/p&gt;

&lt;p&gt;Titre et description (une phrase sur le problème résolu)&lt;/p&gt;

&lt;p&gt;Technologies utilisées (Stack technique visible clairement)&lt;/p&gt;

&lt;p&gt;Ton rôle (seul, en équipe ? quelle partie ?)&lt;/p&gt;

&lt;p&gt;Lien vers le site live (priorité absolue)&lt;/p&gt;

&lt;p&gt;Lien GitHub (code source si public)&lt;/p&gt;

&lt;p&gt;Capture d'écran ou vidéo&lt;/p&gt;

&lt;p&gt;À propos&lt;/p&gt;

&lt;p&gt;Une section personnelle et humaine. Pas une liste de compétences — une vraie présentation de qui tu es, pourquoi tu codes, et ce qui te différencie.&lt;/p&gt;

&lt;p&gt;Compétences&lt;/p&gt;

&lt;p&gt;Ne liste pas 40 technologies. Concentre-toi sur celles où tu es vraiment compétent. Un recruteur qui voit "React ⭐⭐⭐⭐⭐" sur un portfolio et découvre en entretien que tu connais à peine les hooks va blacklister ton profil.&lt;/p&gt;

&lt;p&gt;Contact&lt;/p&gt;

&lt;p&gt;Email visible, lien LinkedIn, lien GitHub. Un formulaire de contact en bonus.&lt;/p&gt;

&lt;p&gt;Quels projets inclure (même sans expérience)&lt;/p&gt;

&lt;p&gt;Projet 1 — Une vraie application fullstack&lt;/p&gt;

&lt;p&gt;Pas un tutoriel, pas un clone basique de Todo List. Une vraie application avec authentification, base de données, et un cas d'usage réel. Exemple : un outil de gestion de budget personnel, une plateforme de réservation, un dashboard analytics.&lt;/p&gt;

&lt;p&gt;Projet 2 — Contribuer à l'open source&lt;/p&gt;

&lt;p&gt;Trouve un projet open source qui t'intéresse et propose une pull request — même petite (correction de bug, amélioration de documentation). GitHub affiche tes contributions et ça prouve que tu sais travailler dans un codebase existant.&lt;/p&gt;

&lt;p&gt;Projet 3 — Un projet pour "un vrai client" (même gratuit)&lt;/p&gt;

&lt;p&gt;L'association sportive de ton quartier, le restaurant de tes parents, le site d'un ami entrepreneur — un projet pour un vrai utilisateur avec de vraies contraintes est 10x plus convaincant qu'un projet personnel fictif.&lt;/p&gt;

&lt;p&gt;Projet 4 — Un projet lié à ta spécialité&lt;/p&gt;

&lt;p&gt;Si tu te spécialises en IA : une application qui utilise une API d'IA. En cybersécurité : un outil de scan de vulnérabilités. En data : un dashboard avec des vraies données.&lt;/p&gt;

&lt;p&gt;Les technologies à utiliser pour votre portfolio&lt;/p&gt;

&lt;p&gt;Pour un portfolio développeur web : React ou Vue.js + Tailwind CSS — moderne, performant, bonne impression&lt;/p&gt;

&lt;p&gt;Hébergement : Vercel ou Netlify — gratuits, déploiement automatique depuis GitHub, domaine personnalisé possible&lt;/p&gt;

&lt;p&gt;Pour se démarquer :&lt;/p&gt;

&lt;p&gt;Animations subtiles (Framer Motion)&lt;/p&gt;

&lt;p&gt;Mode sombre/clair&lt;/p&gt;

&lt;p&gt;Score Lighthouse 90+ (performance, accessibilité, SEO)&lt;/p&gt;

&lt;p&gt;Exemples de portfolios développeurs qui convertissent&lt;/p&gt;

&lt;p&gt;Ce qui caractérise les meilleurs portfolios de développeurs :&lt;/p&gt;

&lt;p&gt;Design sobre et professionnel (pas besoin d'être designer)&lt;/p&gt;

&lt;p&gt;Chargement rapide (moins de 2 secondes)&lt;/p&gt;

&lt;p&gt;Mobile-responsive parfait&lt;/p&gt;

&lt;p&gt;Projets avec des liens live qui fonctionnent (rien de pire qu'un projet "en construction")&lt;/p&gt;

&lt;p&gt;Code source accessible sur GitHub&lt;/p&gt;

&lt;p&gt;Héberger son portfolio gratuitement&lt;/p&gt;

&lt;p&gt;Vercel (recommandé pour React/Next.js) : déploiement en 2 clics depuis GitHub, domaine gratuit en .vercel.app, custom domain supporté.&lt;/p&gt;

&lt;p&gt;Netlify : alternative à Vercel, excellent pour les sites statiques.&lt;/p&gt;

&lt;p&gt;GitHub Pages : gratuit, mais moins flexible. Bien pour un portfolio HTML/CSS simple.&lt;/p&gt;

&lt;p&gt;Domaine personnalisé : investis 10-15€/an dans tonprenom.dev ou tonprenom.io. C'est un signal de sérieux.&lt;/p&gt;

&lt;p&gt;Erreurs classiques à éviter&lt;/p&gt;

&lt;p&gt;Erreur 1 — Projets qui ne fonctionnent pas Rien n'est plus rédhibitoire qu'un projet "live" qui affiche une erreur 404. Teste tout avant de partager.&lt;/p&gt;

&lt;p&gt;Erreur 2 — Trop de projets de mauvaise qualité 3 projets excellents &amp;gt; 10 projets médiocres. Sois sélectif.&lt;/p&gt;

&lt;p&gt;Erreur 3 — Design trop complexe qui noie le contenu Ton portfolio montre que tu sais coder, pas que tu es designer. Un design sobre et propre inspire plus confiance qu'un portfolio "spectaculaire" mais illisible.&lt;/p&gt;

&lt;p&gt;Erreur 4 — Pas de contact visible Certains portfolios sont magnifiques mais il faut chercher pendant 2 minutes pour trouver un email. Met-le sur toutes les pages.&lt;/p&gt;

&lt;p&gt;Erreur 5 — Pas de mise à jour Un portfolio avec "Dernière mise à jour : 2022" donne l'impression que tu n'es plus actif. Mets à jour au moins une fois par an.&lt;/p&gt;

&lt;p&gt;FAQ&lt;/p&gt;

&lt;p&gt;-&amp;gt;Faut-il un portfolio même si on a de l'expérience ?&lt;br&gt;
Absolument. Pour un développeur senior, le portfolio devient moins un catalogue de projets qu'une preuve de ta façon&lt;br&gt;
-&amp;gt;de penser et de communiquer.Combien de temps faut-il pour créer un bon portfolio ?&lt;br&gt;
Un weekend intensif suffit pour un portfolio basique mais professionnel. Ne t'y perd pas trop un portfolio imparfait mis en ligne vaut mieux qu'un portfolio parfait qui n'existe pas encore.Faut-il coder son portfolio from -&amp;gt; scratch ou utiliser un template ?&lt;br&gt;
Pour un développeur, coder son portfolio from scratch envoie un signal fort. Mais un template bien personnalisé est mieux qu'un portfolio raté construit from scratch.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
