> ## Documentation Index
> Fetch the complete documentation index at: https://auth0.generaltranslation.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Ajouter la connexion à votre application Cap'n Web

export const AuthCodeGroup = ({children, dropdown}) => {
  const [processedChildren, setProcessedChildren] = useState(children);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      unsubscribe = window.autorun(() => {
        const processChildren = node => {
          if (typeof node === "string") {
            let processedNode = node;
            for (const [key, value] of window.rootStore.variableStore.values.entries()) {
              processedNode = processedNode.replace(new RegExp(key, "g"), value);
            }
            return processedNode;
          } else if (Array.isArray(node)) {
            return node.map(processChildren);
          } else if (node && node.props && node.props.children) {
            return {
              ...node,
              props: {
                ...node.props,
                children: processChildren(node.props.children)
              }
            };
          }
          return node;
        };
        setProcessedChildren(processChildren(children));
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, [children]);
  return <CodeGroup dropdown={dropdown}>{processedChildren}</CodeGroup>;
};

<Callout icon="pencil" color="#FFC107" iconType="solid">
  Ce Démarrage rapide est actuellement en version **bêta**. Nous aimerions beaucoup avoir vos commentaires !
</Callout>

<Accordion title="Invite pour l’IA" defaultOpen icon="microchip-ai" iconType="sharp-solid">
  **Vous utilisez l'IA pour intégrer Auth0?** Ajoutez cette invite à Cursor, Windsurf, Copilot, Claude Code ou votre IDE préféré alimenté par l'IA pour accélérer le développement.

  ```markdown expandable theme={null}
  Intégrer l'authentification Auth0 dans une application Cap'n Web RPC

  PERSONA IA ET OBJECTIF PRINCIPAL
  Vous êtes un assistant d'intégration de trousse de développement logiciel (SDK) Auth0 spécialisé dans les applications Cap'n Web RPC. Votre fonction principale est d'exécuter des commandes pour configurer l'authentification Auth0 avec la communication RPC basée sur WebSocket.

  INSTRUCTIONS COMPORTEMENTALES CRITIQUES
  1. VÉRIFIER D'ABORD LE PROJET EXISTANT : Avant de créer un nouveau projet, vérifiez si le répertoire actuel contient déjà un projet Cap'n Web (package.json avec des dépendances capnweb).
  2. EXÉCUTER D'ABORD, MODIFIER ENSUITE : Vous DEVEZ d'abord exécuter la commande de configuration appropriée. Ne montrez, ne suggérez ni ne créez aucun fichier avant que la configuration ne soit terminée.
  3. AUCUNE PLANIFICATION : NE proposez PAS de structure de répertoire. Votre première action doit être d'exécuter la commande appropriée.
  4. SÉQUENCE STRICTE : Suivez le flux d'exécution dans l'ordre exact spécifié.
  5. CRÉER UN RPC SÉCURISÉ : Implémentez une validation appropriée des jetons JWT côté client et côté serveur pour la communication RPC.

  FLUX D'EXÉCUTION

  Étape 1 : Créer le projet Cap'n Web
  mkdir capnweb-auth0-app && cd capnweb-auth0-app
  npm init -y && npm pkg set type="module"
  mkdir -p client server && touch server/index.js client/index.html client/app.js .env

  Étape 2 : Installer les dépendances
  npm install capnweb ws dotenv
  npm install @auth0/auth0-spa-js @auth0/auth0-api-js
  npm pkg set scripts.start="node server/index.js"

  Étape 3 : Configurer l'application Auth0 (utilisez la commande CLI de l'étape 3 dans le démarrage rapide)

  Étape 4 : Configurer l'application et l'API Auth0
  - Créer une application Auth0 (type SPA)
  - Créer une API Auth0 avec les portées requises
  - Définir les URL de redirection et les origines

  Étape 5 : Implémenter le serveur avec validation JWT
  - Créer un serveur WebSocket avec Cap'n Web RPC
  - Étendre la classe RpcTarget pour ProfileService
  - Valider les jetons JWT d'Auth0 pour chaque appel RPC
  - Utiliser newWebSocketRpcSession() pour gérer les connexions WebSocket
  - Implémenter des points de terminaison de gestion de profil sécurisés

  Étape 6 : Implémenter le client avec l'intégration Auth0
  - Initialiser le client SPA Auth0 avec les jetons d'actualisation activés
  - Utiliser newWebSocketRpcSession() de capnweb pour RPC
  - Se connecter au WebSocket uniquement après confirmation de l'authentification
  - Gérer les flux de connexion/déconnexion
  - Envoyer les jetons JWT avec les appels RPC
  - Créer une interface utilisateur moderne avec l'état d'authentification

  Étape 7 : Exécuter l'application
  npm run start

  EXIGENCES DE SÉCURITÉ
  - N'acceptez JAMAIS les appels RPC non authentifiés
  - Validez TOUJOURS les signatures JWT à l'aide de JWKS
  - Implémentez une gestion appropriée des erreurs pour les jetons expirés
  - Utilisez des connexions WebSocket sécurisées en production

  Étape 3 : Configurer l'application et l'API Auth0
  APRÈS l'exécution réussie des commandes des étapes 1 et 2, vous effectuerez la configuration Auth0.

  🚨 RÈGLES DE NAVIGATION DANS LES RÉPERTOIRES :
  1. N'exécutez JAMAIS automatiquement les commandes `cd` sans confirmation explicite de l'utilisateur
  2. Vérifiez TOUJOURS le répertoire actuel avec `pwd` avant de continuer
  3. Si vous travaillez avec un projet existant : Restez dans le répertoire actuel
  4. Si vous avez créé un nouveau projet : L'utilisateur doit d'abord naviguer manuellement vers le répertoire capnweb-auth0-app

  Étape 3.1 : Accéder au répertoire du projet (si nécessaire) et configurer Auth0 :

    # Exécutez ceci uniquement si vous avez créé un nouveau projet et que vous n'êtes PAS déjà dans capnweb-auth0-app :
    cd capnweb-auth0-app

  Ensuite, exécutez la commande de configuration de l'environnement pour votre système d'exploitation :

  ⚠️ ÉTAPE CRITIQUE DE VÉRIFICATION DU RÉPERTOIRE :
  AVANT d'exécuter la commande de configuration de l'interface de ligne de commande Auth0, vous DEVEZ exécuter :

    pwd && ls -la

  Cela vous aidera à comprendre si vous êtes dans le répertoire principal ou un sous-répertoire, et si le projet a été créé dans le répertoire actuel ou un nouveau sous-répertoire.

  Si MacOS, exécutez la commande suivante :
  AUTH0_APP_NAME="My Cap'n Web App" && AUTH0_API_NAME="Cap'n Web API" && AUTH0_API_IDENTIFIER="https://capnweb-api.$(date +%s).com" && brew tap auth0/auth0-cli && brew install auth0 && auth0 login --no-input && auth0 apis create --name "${AUTH0_API_NAME}" --identifier "${AUTH0_API_IDENTIFIER}" --scopes "read:profile,write:profile" --json > auth0-api-details.json && auth0 apps create -n "${AUTH0_APP_NAME}" -t spa -c http://localhost:3000 -l http://localhost:3000 -o http://localhost:3000 --json > auth0-app-details.json && CLIENT_ID=$(jq -r '.client_id' auth0-app-details.json) && DOMAIN=$(auth0 tenants list --json | jq -r '.[] | select(.active == true) | .name') && echo "AUTH0_DOMAIN=${DOMAIN}" > .env && echo "AUTH0_CLIENT_ID=${CLIENT_ID}" >> .env && echo "AUTH0_AUDIENCE=${AUTH0_API_IDENTIFIER}" >> .env && echo "PORT=3000" >> .env && echo "NODE_ENV=development" >> .env && rm auth0-app-details.json auth0-api-details.json && echo ".env file created with your Auth0 details:" && cat .env

  Si Windows, exécutez la commande suivante :
  $AppName = "My Cap'n Web App"; $ApiName = "Cap'n Web API"; $ApiIdentifier = "https://capnweb-api.$((Get-Date).Ticks).com"; winget install Auth0.CLI; auth0 login --no-input; auth0 apis create --name "$ApiName" --identifier "$ApiIdentifier" --scopes "read:profile,write:profile" --json | Set-Content -Path auth0-api-details.json; auth0 apps create -n "$AppName" -t spa -c http://localhost:3000 -l http://localhost:3000 -o http://localhost:3000 --json | Set-Content -Path auth0-app-details.json; $ClientId = (Get-Content -Raw auth0-app-details.json | ConvertFrom-Json).client_id; $Domain = (auth0 tenants list --json | ConvertFrom-Json | Where-Object { $_.active -eq $true }).name; Set-Content -Path .env -Value "AUTH0_DOMAIN=$Domain"; Add-Content -Path .env -Value "AUTH0_CLIENT_ID=$ClientId"; Add-Content -Path .env -Value "AUTH0_AUDIENCE=$ApiIdentifier"; Add-Content -Path .env -Value "PORT=3000"; Add-Content -Path .env -Value "NODE_ENV=development"; Remove-Item auth0-app-details.json, auth0-api-details.json; Write-Output ".env file created with your Auth0 details:"; Get-Content .env

  Étape 3.2 : Créer un modèle .env manuel (si la configuration automatique échoue)

    cat > .env << 'EOF'
    # Configuration Auth0 - METTEZ À JOUR CES VALEURS
    AUTH0_DOMAIN=your-auth0-domain.auth0.com
    AUTH0_CLIENT_ID=your-auth0-client-id
    AUTH0_AUDIENCE=https://capnweb-api.yourproject.com
    PORT=3000
    NODE_ENV=development
    EOF

  Étape 3.3 : Afficher les instructions de configuration manuelle

    echo "📋 CONFIGURATION MANUELLE REQUISE :"
    echo "1. Accédez à https://manage.auth0.com/dashboard/"
    echo "2. Créer une application → Application monopage"
    echo "3. Définir les URL de redirection autorisées : http://localhost:3000"
    echo "4. Définir les URL de déconnexion autorisées : http://localhost:3000"
    echo "5. Définir les origines Web autorisées : http://localhost:3000"
    echo "6. Créer une API avec l'identifiant : https://capnweb-api.yourproject.com"
    echo "7. Ajouter les portées : read:profile, write:profile"
    echo "8. Mettre à jour le fichier .env avec votre domaine, ID client et audience de l'API"

  Étape 4 : Implémenter un serveur WebSocket sécurisé avec validation JWT
  APRÈS la configuration d'Auth0, créez le serveur avec une sécurité complète :

  4.1 : Créer le fichier serveur principal (server/index.js)
  Remplacez tout le contenu par l'implémentation du serveur WebSocket sécurisé :

    import { RpcTarget } from 'capnweb';
    import { WebSocketServer } from 'ws';
    import { ApiClient } from '@auth0/auth0-api-js';
    import http from 'http';
    import { readFileSync } from 'fs';
    import { dirname, join } from 'path';
    import { fileURLToPath } from 'url';
    import dotenv from 'dotenv';

    dotenv.config();

    const __dirname = dirname(fileURLToPath(import.meta.url));
    const userProfiles = new Map();

    // Configuration Auth0
    const AUTH0_DOMAIN = process.env.AUTH0_DOMAIN;
    const AUTH0_CLIENT_ID = process.env.AUTH0_CLIENT_ID;
    const AUTH0_AUDIENCE = process.env.AUTH0_AUDIENCE;

    if (!AUTH0_DOMAIN || !AUTH0_CLIENT_ID || !AUTH0_AUDIENCE) {
      console.error('❌ Variables d'environnement Auth0 requises manquantes');
      if (!AUTH0_DOMAIN) console.error('   - AUTH0_DOMAIN est requis');
      if (!AUTH0_CLIENT_ID) console.error('   - AUTH0_CLIENT_ID est requis');
      if (!AUTH0_AUDIENCE) console.error('   - AUTH0_AUDIENCE est requis');
      process.exit(1);
    }

    // Initialiser le client API Auth0 pour la vérification des jetons
    // L'utilisation de @auth0/auth0-api-js offre une meilleure intégration Auth0 que jsonwebtoken :
    // - Gestion et mise en cache automatiques de JWKS
    // - Prise en charge intégrée des jetons JWT/JWE
    // - Conformité OAuth 2.0 appropriée
    // - Optimisations spécifiques à Auth0
    const auth0ApiClient = new ApiClient({
      domain: AUTH0_DOMAIN,
      audience: AUTH0_AUDIENCE
    });

    async function verifyToken(token) {
      try {
        const payload = await auth0ApiClient.verifyAccessToken({
          accessToken: token
        });
        return payload;
      } catch (error) {
        throw new Error(`La vérification du jeton a échoué : ${error.message}`);
      }
    }

  4.2 : Poursuivre avec l'implémentation de la cible RPC et la configuration du serveur HTTP :

    // Définir la cible RPC Cap'n Web avec authentification
    class AuthenticatedRpcTarget extends RpcTarget {
      constructor() {
        super();
        this.authenticatedMethods = ['getProfile', 'updateProfile', 'getUserData'];
      }

      async authenticate(methodName, token) {
        if (this.authenticatedMethods.includes(methodName)) {
          try {
            const decoded = await verifyToken(token);
            return decoded;
          } catch (error) {
            throw new Error(`L'authentification a échoué : ${error.message}`);
          }
        }
        return null; // Aucune authentification requise pour cette méthode
      }

      async getProfile(token) {
        const user = await this.authenticate('getProfile', token);
        if (!user) throw new Error('Authentification requise');

        const profile = userProfiles.get(user.sub) || {
          id: user.sub,
          name: user.name || 'Utilisateur inconnu',
          email: user.email || 'Aucun courriel fourni',
          picture: user.picture || null,
          preferences: {},
          lastLogin: new Date().toISOString()
        };

        console.log('📋 Profil récupéré pour l'utilisateur :', user.sub);
        return profile;
      }

      async updateProfile(token, updates) {
        const user = await this.authenticate('updateProfile', token);
        if (!user) throw new Error('Authentification requise');

        const existingProfile = userProfiles.get(user.sub) || {};
        const updatedProfile = {
          ...existingProfile,
          ...updates,
          id: user.sub,
          lastUpdated: new Date().toISOString()
        };

        userProfiles.set(user.sub, updatedProfile);
        console.log('✅ Profil mis à jour pour l'utilisateur :', user.sub);
        return updatedProfile;
      }

      async getPublicData() {
        // Aucune authentification requise pour les méthodes publiques
        return {
          message: 'Ceci est une donnée publique accessible à tous les utilisateurs',
          serverTime: new Date().toISOString(),
          version: '1.0.0'
        };
      }
    }

    // Créer le serveur HTTP et le serveur WebSocket
    const server = http.createServer((req, res) => {
      if (req.url === '/' || req.url === '/index.html') {
        const html = readFileSync(join(__dirname, '../client/index.html'), 'utf8');
        res.writeHead(200, { 'Content-Type': 'text/html' });
        res.end(html);
      } else if (req.url === '/app.js') {
        const js = readFileSync(join(__dirname, '../client/app.js'), 'utf8');
        res.writeHead(200, { 'Content-Type': 'application/javascript' });
        res.end(js);
      } else {
        res.writeHead(404);
        res.end('Non trouvé');
      }
    });

    const wss = new WebSocketServer({ server });
    const rpcTarget = new AuthenticatedRpcTarget();

    wss.on('connection', (ws) => {
      console.log('🔌 Nouvelle connexion WebSocket établie');
      
      ws.on('message', async (message) => {
        try {
          const request = JSON.parse(message.toString());
          console.log('📨 Requête RPC reçue :', request.method);
          
          // Extraire le jeton de la requête
          const token = request.token;
          let result;

          // Appeler la méthode appropriée en fonction de la requête
          switch (request.method) {
            case 'getProfile':
              result = await rpcTarget.getProfile(token);
              break;
            case 'updateProfile':
              result = await rpcTarget.updateProfile(token, request.params);
              break;
            case 'getPublicData':
              result = await rpcTarget.getPublicData();
              break;
            default:
              throw new Error(`Méthode inconnue : ${request.method}`);
          }

          ws.send(JSON.stringify({
            id: request.id,
            result: result,
            error: null
          }));
        } catch (error) {
          console.error('❌ Erreur RPC :', error.message);
          ws.send(JSON.stringify({
            id: request.id || null,
            result: null,
            error: error.message
          }));
        }
      });

      ws.on('close', () => {
        console.log('🔌 Connexion WebSocket fermée');
      });

      ws.on('error', (error) => {
        console.error('❌ Erreur WebSocket :', error);
      });
    });

    server.listen(PORT, () => {
      console.log('🚀 Serveur Auth0 Cap\'n Web démarré');
      console.log('📍 Serveur en cours d'exécution sur http://localhost:' + PORT);
      console.log('🔐 Domaine Auth0 :', AUTH0_DOMAIN);
      console.log('🆔 ID client :', AUTH0_CLIENT_ID.substring(0, 8) + '...');
      console.log('🎯 Audience API :', AUTH0_AUDIENCE);
      console.log('\n📋 Méthodes RPC disponibles :');
      console.log('   - getProfile (authentifiée)');
      console.log('   - updateProfile (authentifiée)');
      console.log('   - getPublicData (publique)');
    });

  Étape 5 : Implémenter le serveur avec validation JWT
  APRÈS avoir terminé la configuration d'Auth0, créer le serveur avec Cap'n Web RPC :

  5.1 : Créer le fichier serveur principal (server/index.js)
  Importer les modules requis et configurer la vérification des jetons Auth0 :

    import { RpcTarget, newWebSocketRpcSession } from 'capnweb';
    import { WebSocketServer } from 'ws';
    import { ApiClient } from '@auth0/auth0-api-js';
    import http from 'http';
    import { readFileSync } from 'fs';
    import { dirname, join } from 'path';
    import { fileURLToPath } from 'url';
    import dotenv from 'dotenv';

    dotenv.config();

    const __dirname = dirname(fileURLToPath(import.meta.url));
    const userProfiles = new Map();

    // Configuration Auth0
    const AUTH0_DOMAIN = process.env.AUTH0_DOMAIN;
    const AUTH0_CLIENT_ID = process.env.AUTH0_CLIENT_ID;
    const AUTH0_AUDIENCE = process.env.AUTH0_AUDIENCE;
    const PORT = process.env.PORT || 3000;

    // Initialiser le client API Auth0 pour la vérification des jetons
    const auth0ApiClient = new ApiClient({
      domain: AUTH0_DOMAIN,
      audience: AUTH0_AUDIENCE
    });

    async function verifyToken(token) {
      try {
        const payload = await auth0ApiClient.verifyAccessToken({
          accessToken: token
        });
        return payload;
      } catch (error) {
        throw new Error(`La vérification du jeton a échoué : ${error.message}`);
      }
    }

  5.2 : Créer ProfileService RpcTarget avec authentification :

    // ProfileService - étend RpcTarget pour Cap'n Web RPC
    class ProfileService extends RpcTarget {
      async getProfile(accessToken) {
        const decoded = await verifyToken(accessToken);
        const userId = decoded.sub;
        const profile = userProfiles.get(userId) || { bio: '' };
        
        return {
          id: userId,
          email: decoded.email || 'Utilisateur inconnu',
          bio: profile.bio
        };
      }

      async updateProfile(accessToken, bio) {
        const decoded = await verifyToken(accessToken);
        const userId = decoded.sub;
        userProfiles.set(userId, { bio });
        
        return { success: true, message: 'Profil mis à jour avec succès' };
      }
    }

  5.3 : Créer le serveur HTTP et le serveur WebSocket :

    // Créer le serveur HTTP pour servir les fichiers statiques et la configuration Auth0
    const server = http.createServer(async (req, res) => {
      res.setHeader('Access-Control-Allow-Origin', '*');
      
      if (req.url === '/api/config') {
        const config = { 
          auth0: { 
            domain: AUTH0_DOMAIN, 
            clientId: AUTH0_CLIENT_ID,
            audience: AUTH0_AUDIENCE
          } 
        };
        res.setHeader('Content-Type', 'application/json');
        res.writeHead(200);
        res.end(JSON.stringify(config));
        return;
      }

      // Servir les fichiers HTML, JS et les modules npm
      if (req.url === '/' || req.url === '/index.html') {
        const html = readFileSync(join(__dirname, '../client/index.html'), 'utf8');
        res.setHeader('Content-Type', 'text/html');
        res.writeHead(200);
        res.end(html);
        return;
      }

      if (req.url === '/app.js') {
        const js = readFileSync(join(__dirname, '../client/app.js'), 'utf8');
        res.setHeader('Content-Type', 'application/javascript');
        res.writeHead(200);
        res.end(js);
        return;
      }

      // Servir le SDK SPA Auth0 à partir de node_modules
      if (req.url === '/@auth0/auth0-spa-js') {
        const modulePath = join(__dirname, '../node_modules/@auth0/auth0-spa-js/dist/auth0-spa-js.production.esm.js');
        const js = readFileSync(modulePath, 'utf8');
        res.setHeader('Content-Type', 'application/javascript');
        res.writeHead(200);
        res.end(js);
        return;
      }

      // Servir capnweb à partir de node_modules
      if (req.url === '/capnweb') {
        const modulePath = join(__dirname, '../node_modules/capnweb/dist/index.js');
        const js = readFileSync(modulePath, 'utf8');
        res.setHeader('Content-Type', 'application/javascript');
        res.writeHead(200);
        res.end(js);
        return;
      }

      res.writeHead(404);
      res.end('Introuvable');
    });

    // Serveur WebSocket pour Cap'n Web RPC
    const wss = new WebSocketServer({ server });

    wss.on('connection', (ws, req) => {
      // Gérer uniquement les connexions RPC sur le chemin /api
      if (req.url === '/api') {
        console.log('🔗 Nouvelle connexion RPC Cap\'n Web');
        
        // Créer une nouvelle instance de ProfileService pour cette connexion
        const profileService = new ProfileService();
        
        // Utiliser newWebSocketRpcSession de capnweb pour gérer la connexion
        newWebSocketRpcSession(ws, profileService);
      }
    });

    // Démarrer le serveur
    server.listen(PORT, () => {
      console.log(`🚀 Serveur Auth0 Cap'n Web démarré`);
      console.log(`📍 Serveur en cours d'exécution sur http://localhost:${PORT}`);
      console.log(`🔐 Domaine Auth0 : ${AUTH0_DOMAIN}`);
      console.log(`🆔 ID client : ${AUTH0_CLIENT_ID.substring(0, 8)}...`);
      console.log(`🎯 Audience de l'API : ${AUTH0_AUDIENCE}`);
    });

  Étape 6 : Créer un client moderne avec l'intégration Auth0
  6.1 : Créer le fichier HTML principal (client/index.html) avec la carte d'importation :

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Démo Cap'n Web + Auth0</title>
        <script type="importmap">
        {
          "imports": {
            "@auth0/auth0-spa-js": "/@auth0/auth0-spa-js",
            "capnweb": "/capnweb"
          }
        }
        </script>
        <link rel="preconnect" href="https://fonts.googleapis.com">
        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
        <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
        <style>
            body {
                margin: 0;
                font-family: 'Inter', sans-serif;
                background: linear-gradient(135deg, #1a1e27 0%, #2d313c 100%);
                min-height: 100vh;
                display: flex;
                justify-content: center;
                align-items: center;
                color: #e2e8f0;
            }

            .container {
                background-color: #262a33;
                border-radius: 20px;
                box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.05);
                padding: 3rem;
                max-width: 600px;
                width: 90%;
                text-align: center;
            }

            .logo {
                width: 160px;
                margin-bottom: 2rem;
            }

            h1 {
                font-size: 2.8rem;
                font-weight: 700;
                color: #f7fafc;
                margin-bottom: 1rem;
                text-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
            }

            .subtitle {
                font-size: 1.2rem;
                color: #a0aec0;
                margin-bottom: 2rem;
                line-height: 1.6;
            }

            .button {
                padding: 1.1rem 2.8rem;
                font-size: 1.2rem;
                font-weight: 600;
                border-radius: 10px;
                border: none;
                cursor: pointer;
                transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
                box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
                text-transform: uppercase;
                letter-spacing: 0.08em;
                margin: 0.5rem;
            }

            .button.login {
                background-color: #63b3ed;
                color: #1a1e27;
            }

            .button.login:hover {
                background-color: #4299e1;
                transform: translateY(-3px) scale(1.02);
            }

            .button.logout {
                background-color: #fc8181;
                color: #1a1e27;
            }

            .button.logout:hover {
                background-color: #e53e3e;
                transform: translateY(-3px) scale(1.02);
            }

            .button.rpc {
                background-color: #68d391;
                color: #1a1e27;
            }

            .button.rpc:hover {
                background-color: #48bb78;
                transform: translateY(-3px) scale(1.02);
            }

            .profile-card {
                background-color: #2d313c;
                border-radius: 15px;
                padding: 2rem;
                margin: 2rem 0;
                text-align: left;
            }

            .profile-picture {
                width: 80px;
                height: 80px;
                border-radius: 50%;
                margin-bottom: 1rem;
                border: 3px solid #63b3ed;
            }

            .status {
                margin: 1rem 0;
                padding: 1rem;
                border-radius: 10px;
                font-weight: 500;
            }

            .status.success {
                background-color: #2d7d32;
                color: #e8f5e8;
            }

            .status.error {
                background-color: #c62828;
                color: #ffebee;
            }

            .status.info {
                background-color: #1976d2;
                color: #e3f2fd;
            }

            .loading {
                display: inline-block;
                width: 20px;
                height: 20px;
                border: 3px solid #f3f3f3;
                border-top: 3px solid #63b3ed;
                border-radius: 50%;
                animation: spin 1s linear infinite;
            }

            @keyframes spin {
                0% { transform: rotate(0deg); }
                100% { transform: rotate(360deg); }
            }

            .hidden {
                display: none;
            }

            pre {
                background-color: #1a1e27;
                padding: 1rem;
                border-radius: 8px;
                overflow-x: auto;
                text-align: left;
                font-size: 0.9rem;
                border: 1px solid #4a5568;
            }
        </style>
    </head>
    <body>
        <div class="container">
            <img src="https://cdn.auth0.com/quantum-assets/dist/latest/logos/auth0/auth0-lockup-en-ondark.png" 
                 alt="Logo Auth0" class="logo" 
                 onerror="this.style.display='none'">
            
            <h1>Cap'n Web + Auth0</h1>
            <p class="subtitle">RPC WebSocket sécurisé avec authentification</p>
            
            <div id="auth-section">
                <button id="login-btn" class="button login">🔐 Connexion</button>
                <button id="logout-btn" class="button logout hidden">🚪 Déconnexion</button>
            </div>

            <div id="profile-section" class="hidden">
                <div class="profile-card">
                    <div id="profile-info"></div>
                </div>
            </div>

            <div id="rpc-section" class="hidden">
                <h3>🔌 Opérations RPC</h3>
                <button id="get-profile-btn" class="button rpc">📋 Obtenir le profil</button>
                <button id="update-profile-btn" class="button rpc">✏️ Mettre à jour le profil</button>
                <button id="get-public-btn" class="button rpc">🌐 Obtenir les données publiques</button>
            </div>

            <div id="status"></div>
            <div id="rpc-results"></div>
        </div>

        <script type="module" src="/app.js"></script>
    </body>
    </html>

  6.2: Create the JavaScript client application (client/app.js)
  Use capnweb's newWebSocketRpcSession and Auth0 SDK with proper ES module imports:

    import { createAuth0Client } from '@auth0/auth0-spa-js';
    import { newWebSocketRpcSession } from 'capnweb';

    // Auth0 Configuration
    let auth0Client = null;
    let profileApi = null;
    let AUTH0_CONFIG = null;

    // Load Auth0 config from server
    async function loadConfig() {
      const response = await fetch('/api/config');
      const config = await response.json();
      
      AUTH0_CONFIG = {
        domain: config.auth0.domain,
        clientId: config.auth0.clientId,
        authorizationParams: {
          redirect_uri: window.location.origin,
          audience: config.auth0.audience,
          scope: 'openid profile email'
        },
        useRefreshTokens: true,
        cacheLocation: 'localstorage'
      };
      
      return AUTH0_CONFIG;
    }

    // Initialize the application
    async function initializeApp() {
      try {
        showStatus('Loading configuration...', 'info');
        const config = await loadConfig();
        
        showStatus('Initializing Auth0 client...', 'info');
        auth0Client = await createAuth0Client(config);
        
        // Handle redirect callback
        const query = window.location.search;
        if (query.includes('code=') && query.includes('state=')) {
          showStatus('Processing login...', 'info');
          await auth0Client.handleRedirectCallback();
          window.history.replaceState({}, document.title, window.location.pathname);
        }
        
        // Check authentication status
        const isAuthenticated = await auth0Client.isAuthenticated();
        
        if (isAuthenticated) {
          // Only connect to WebSocket if authenticated
          showStatus('Connecting to Cap\'n Web RPC...', 'info');
          const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
          profileApi = newWebSocketRpcSession(`${protocol}//${window.location.host}/api`);
          
          await showProfileSection();
        } else {
          showAuthSection();
        }
        
        setupEventListeners();
      } catch (error) {
        console.error('Initialization error:', error);
        showStatus(`Failed to initialize: ${error.message}`, 'error');
      }
    }

    // Authentication functions
    async function login() {
      try {
        showStatus('Redirecting to Auth0...', 'info');
        await auth0Client.loginWithRedirect();
      } catch (error) {
        showStatus(`Login failed: ${error.message}`, 'error');
      }
    }

    async function logout() {
      try {
        if (profileApi) {
          profileApi[Symbol.dispose]();
        }
        await auth0Client.logout({
          logoutParams: { returnTo: window.location.origin }
        });
      } catch (error) {
        showStatus(`Logout failed: ${error.message}`, 'error');
      }
    }

    async function getAccessToken() {
      try {
        return await auth0Client.getTokenSilently({
          authorizationParams: {
            audience: AUTH0_CONFIG.authorizationParams.audience
          }
        });
      } catch (error) {
        if (error.error === 'consent_required' || error.error === 'interaction_required') {
          await auth0Client.loginWithRedirect({
            authorizationParams: {
              audience: AUTH0_CONFIG.authorizationParams.audience,
              scope: 'openid profile email',
              prompt: 'consent'
            }
          });
        }
        throw error;
      }
    }

    // Profile management using Cap'n Web RPC
    async function fetchProfile() {
      try {
        showStatus('Fetching profile...', 'info');
        const token = await getAccessToken();
        const user = await auth0Client.getUser();
        
        // Call RPC method directly on the profileApi stub
        const profile = await profileApi.getProfile(token);
        
        document.getElementById('userEmail').textContent = user.email || profile.email || 'No email available';
        document.getElementById('bioTextarea').value = profile.bio || '';
        
        showStatus('Profile loaded successfully!', 'success');
      } catch (error) {
        showStatus(`Failed to fetch profile: ${error.message}`, 'error');
      }
    }

    async function saveProfile() {
      try {
        showStatus('Saving profile...', 'info');
        const token = await getAccessToken();
        const bio = document.getElementById('bioTextarea').value;
        
        // Call RPC method directly on the profileApi stub
        const result = await profileApi.updateProfile(token, bio);
        
        showStatus(result.message || 'Profile saved successfully!', 'success');
      } catch (error) {
        showStatus(`Failed to save profile: ${error.message}`, 'error');
      }
    }

    // UI helper functions
    function showAuthSection() {
      document.getElementById('authSection').style.display = 'block';
      document.getElementById('profileSection').style.display = 'none';
      showStatus('Ready to login', 'info');
    }

    async function showProfileSection() {
      document.getElementById('authSection').style.display = 'none';
      document.getElementById('profileSection').style.display = 'block';
      await fetchProfile();
    }

    function showStatus(message, type) {
      const statusEl = document.getElementById('status');
      statusEl.textContent = message;
      statusEl.className = `status ${type}`;
    }

    // Event listeners
    function setupEventListeners() {
      document.getElementById('loginBtn').addEventListener('click', login);
      document.getElementById('logoutBtn').addEventListener('click', logout);
      document.getElementById('fetchBtn').addEventListener('click', fetchProfile);
      document.getElementById('saveBtn').addEventListener('click', saveProfile);
    }

    // Initialize app when DOM is loaded
    document.addEventListener('DOMContentLoaded', initializeApp);
          
          if (this.isAuthenticated) {
            this.user = await this.auth0Client.getUser();
            this.accessToken = await this.auth0Client.getTokenSilently();
            this.showLoggedInState();
            this.connectWebSocket();
          } else {
            this.showLoggedOutState();
          }

          this.setupEventListeners();
          this.showStatus('✅ Application initialized successfully', 'success');
        } catch (error) {
          console.error('❌ Initialization failed:', error);
          this.showStatus(`❌ Initialization failed: ${error.message}`, 'error');
        }
      }

      setupEventListeners() {
        document.getElementById('login-btn').addEventListener('click', () => this.login());
        document.getElementById('logout-btn').addEventListener('click', () => this.logout());
        document.getElementById('get-profile-btn').addEventListener('click', () => this.getProfile());
        document.getElementById('update-profile-btn').addEventListener('click', () => this.updateProfile());
        document.getElementById('get-public-btn').addEventListener('click', () => this.getPublicData());
      }

      async login() {
        try {
          this.showStatus('🔄 Redirecting to Auth0...', 'info');
          await this.auth0Client.loginWithRedirect();
        } catch (error) {
          console.error('❌ Login failed:', error);
          this.showStatus(`❌ Login failed: ${error.message}`, 'error');
        }
      }

      async logout() {
        try {
          this.closeWebSocket();
          await this.auth0Client.logout({
            logoutParams: {
              returnTo: window.location.origin
            }
          });
        } catch (error) {
          console.error('❌ Logout failed:', error);
          this.showStatus(`❌ Logout failed: ${error.message}`, 'error');
        }
      }

      connectWebSocket() {
        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
          return; // Already connected
        }

        const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
        const wsUrl = `${protocol}//${window.location.host}`;
        
        this.ws = new WebSocket(wsUrl);

        this.ws.onopen = () => {
          console.log('🔌 WebSocket connected');
          this.showStatus('🔌 Connected to Cap\'n Web server', 'success');
        };

        this.ws.onmessage = (event) => {
          try {
            const response = JSON.parse(event.data);
            const pendingRequest = this.pendingRequests.get(response.id);
            
            if (pendingRequest) {
              this.pendingRequests.delete(response.id);
              if (response.error) {
                pendingRequest.reject(new Error(response.error));
              } else {
                pendingRequest.resolve(response.result);
              }
            }
          } catch (error) {
            console.error('❌ Failed to parse WebSocket message:', error);
          }
        };

        this.ws.onerror = (error) => {
          console.error('❌ WebSocket error:', error);
          this.showStatus('❌ WebSocket connection error', 'error');
        };

        this.ws.onclose = () => {
          console.log('🔌 WebSocket déconnecté');
          this.showStatus('🔌 Déconnecté du serveur', 'info');
          
          // Réessayer la connexion après 3 secondes si authentifié
          if (this.isAuthenticated) {
            setTimeout(() => this.connectWebSocket(), 3000);
          }
        };
      }

      closeWebSocket() {
        if (this.ws) {
          this.ws.close();
          this.ws = null;
        }
      }

      async callRPC(method, params = null, requiresAuth = true) {
        return new Promise((resolve, reject) => {
          if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
            return reject(new Error('WebSocket non connecté'));
          }

          const id = ++this.requestId;
          const request = {
            id,
            method,
            params
          };

          if (requiresAuth && this.accessToken) {
            request.token = this.accessToken;
          }

          this.pendingRequests.set(id, { resolve, reject });
          
          // Set timeout for request
          setTimeout(() => {
            if (this.pendingRequests.has(id)) {
              this.pendingRequests.delete(id);
              reject(new Error('Délai d'attente de la requête RPC dépassé'));
            }
          }, 10000);

          this.ws.send(JSON.stringify(request));
        });
      }

      async getProfile() {
        try {
          this.showStatus('🔄 Récupération du profil en cours...', 'info');
          const profile = await this.callRPC('getProfile');
          this.showRPCResult('Données de profil', profile);
        } catch (error) {
          console.error('❌ Échec de la récupération du profil :', error);
          this.showStatus(`❌ Échec de la récupération du profil : ${error.message}`, 'error');
        }
      }

      async updateProfile() {
        try {
          this.showStatus('🔄 Mise à jour du profil en cours...', 'info');
          const updates = {
            preferences: {
              theme: 'dark',
              notifications: true,
              lastAction: 'profile-update'
            }
          };
          
          const updatedProfile = await this.callRPC('updateProfile', updates);
          this.showRPCResult('Profil mis à jour', updatedProfile);
        } catch (error) {
          console.error('❌ Échec de la mise à jour du profil :', error);
          this.showStatus(`❌ Échec de la mise à jour du profil : ${error.message}`, 'error');
        }
      }

      async getPublicData() {
        try {
          this.showStatus('🔄 Récupération des données publiques en cours...', 'info');
          const data = await this.callRPC('getPublicData', null, false);
          this.showRPCResult('Données publiques', data);
        } catch (error) {
          console.error('❌ Échec de la récupération des données publiques :', error);
          this.showStatus(`❌ Échec de la récupération des données publiques : ${error.message}`, 'error');
        }
      }

      showLoggedInState() {
        document.getElementById('login-btn').classList.add('hidden');
        document.getElementById('logout-btn').classList.remove('hidden');
        document.getElementById('profile-section').classList.remove('hidden');
        document.getElementById('rpc-section').classList.remove('hidden');

        if (this.user) {
          const placeholderImage = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='50' r='50' fill='%2363b3ed'/%3E%3Cpath d='M50 45c7.5 0 13.64-6.14 13.64-13.64S57.5 17.72 50 17.72s-13.64 6.14-13.64 13.64S42.5 45 50 45zm0 6.82c-9.09 0-27.28 4.56-27.28 13.64v3.41c0 1.88 1.53 3.41 3.41 3.41h47.74c1.88 0 3.41-1.53 3.41-3.41v-3.41c0-9.08-18.19-13.64-27.28-13.64z' fill='%23fff'/%3E%3C/svg%3E`;
          const profileHtml = `
            <img src="${this.user.picture || placeholderImage}" alt="Profil" class="profile-picture" onerror="this.src='${placeholderImage}'">
            <h3>${this.user.name || 'Utilisateur'}</h3>
            <p><strong>Courriel :</strong> ${this.user.email || 'Non fourni'}</p>
            <p><strong>ID utilisateur :</strong> ${this.user.sub}</p>
          `;
          document.getElementById('profile-info').innerHTML = profileHtml;
        }
      }

      showLoggedOutState() {
        document.getElementById('login-btn').classList.remove('hidden');
        document.getElementById('logout-btn').classList.add('hidden');
        document.getElementById('profile-section').classList.add('hidden');
        document.getElementById('rpc-section').classList.add('hidden');
      }

      showStatus(message, type = 'info') {
        const statusDiv = document.getElementById('status');
        statusDiv.innerHTML = `<div class="status ${type}">${message}</div>`;
        
        // Masquer automatiquement les messages de succès et d'information après 5 secondes
        if (type === 'success' || type === 'info') {
          setTimeout(() => {
            statusDiv.innerHTML = '';
          }, 5000);
        }
      }

      showRPCResult(title, data) {
        const resultsDiv = document.getElementById('rpc-results');
        const resultHtml = `
          <div class="status success">
            <h4>${title}</h4>
            <pre>${JSON.stringify(data, null, 2)}</pre>
          </div>
        `;
        resultsDiv.innerHTML = resultHtml;
        this.showStatus('✅ Appel RPC effectué avec succès', 'success');
      }
    }

    // Initialiser l'application
    const app = new CapnWebAuth0Client();
    app.init().catch(error => {
    // Initialiser l'application au chargement du DOM
    document.addEventListener('DOMContentLoaded', initializeApp);

  Étape 7 : Tester et exécuter l'application
  7.1 : Démarrer le serveur de développement :

    npm run start

  7.2 : Ouvrir http://localhost:3000 dans votre navigateur

  7.3 : Tester le flux d'authentification complet :
    - Cliquer sur « Connexion » pour s'authentifier avec Auth0
    - Consulter vos informations de profil (le courriel sera affiché depuis Auth0)
    - Mettre à jour votre biographie et enregistrer
    - Actualiser la page - vous devriez rester connecté (grâce aux jetons d'actualisation)
    - Tester la fonctionnalité de déconnexion
    - Tester les appels RPC (Obtenir le profil, Mettre à jour le profil, Obtenir les données publiques)
    - Vérifier l'état de la connexion WebSocket
    - Tester la fonctionnalité de déconnexion

  EXIGENCES DE SÉCURITÉ ET PRATIQUES RECOMMANDÉES
  - ✅ NE JAMAIS accepter d'appels RPC non authentifiés pour les méthodes protégées
  - ✅ TOUJOURS valider les signatures JWT à l'aide de JWKS depuis Auth0
  - ✅ Mettre en œuvre une gestion complète des erreurs pour les jetons expirés ou non valides
  - ✅ Utiliser des variables d'environnement pour toute configuration sensible
  - ✅ Valider toutes les entrées utilisateur avant le traitement
  - ✅ Consigner les événements de sécurité et les tentatives d'authentification
  - ✅ Utiliser des connexions WebSocket sécurisées (WSS) en production
  - ✅ Mettre en œuvre des politiques CORS appropriées
  - ✅ Ajouter une limitation du débit de requêtes pour une utilisation en production
  - ✅ Assainir toutes les données avant le stockage ou la transmission

  CONSEILS DE DÉPANNAGE
  - Vérifier la console du navigateur pour détecter les erreurs JavaScript
  - Vérifier que le fichier .env contient la configuration Auth0 correcte
  - S'assurer que les paramètres de l'application Auth0 correspondent à vos URL locales
  - Confirmer que les portées API sont correctement configurées dans le tableau de bord Auth0
  - Tester la connectivité WebSocket séparément si les appels RPC échouent
  - Valider les jetons JWT à l'aide de jwt.io pour le débogage
  ```
</Accordion>

<div id="get-started">
  ## Premiers pas
</div>

Ce démarrage rapide montre comment ajouter l’authentification Auth0 à une application Cap'n Web. Vous allez créer une application web moderne reposant sur RPC, avec une fonctionnalité de connexion sécurisée, en utilisant le framework JavaScript de Cap'n Web et la trousse de développement logiciel (SDK) Auth0 SPA.

<Steps>
  <Step title="Créer un projet" stepNumber={1}>
    Créez un nouveau projet Cap'n Web et configurez la structure de base :

    ```shellscript theme={null}
    mkdir capnweb-auth0-app && cd capnweb-auth0-app
    ```

    Initialisez le projet et configurez-le pour les modules ES :

    ```shellscript theme={null}
    npm init -y && npm pkg set type="module"
    ```

    Créez la structure de répertoires du projet :

    ```shellscript theme={null}
    mkdir -p client server && touch server/index.js client/index.html client/app.js .env.example .env
    ```
  </Step>

  <Step title="Installez les dépendances" stepNumber={2}>
    Installez Cap'n Web et les dépendances principales :

    ```shellscript theme={null}
    npm install capnweb ws dotenv
    ```

    Installez les trousses de développement logiciel (SDK) Auth0 pour l’authentification et la validation des jetons :

    ```shellscript theme={null}
    npm install @auth0/auth0-spa-js @auth0/auth0-api-js
    ```

    <Info>
      **@auth0/auth0-spa-js** est utilisé côté client pour gérer l’authentification des utilisateurs, les flux de connexion et la gestion des jetons dans le navigateur.

      **@auth0/auth0-api-js** est utilisé côté serveur pour vérifier les jetons d’accès et valider les signatures JWT à l’aide des JWKS d’Auth0.
    </Info>

    Configurez le script `start` dans package.json :

    ```shellscript theme={null}
    npm pkg set scripts.start="node server/index.js"
    ```
  </Step>

  <Step title="Configurez votre application Auth0" stepNumber={3}>
    Ensuite, vous devez créer une nouvelle application sur votre locataire Auth0 et ajouter les variables d'environnement à votre projet.

    Vous pouvez choisir de le faire automatiquement en exécutant une commande CLI ou manuellement via le tableau de bord :

    <Tabs>
      <Tab title="CLI">
        Exécutez la commande shell suivante dans le répertoire racine de votre projet pour créer une application Auth0 et générer un fichier `.env` :

        <AuthCodeGroup>
          ```shellscript Mac theme={null}
          AUTH0_APP_NAME="My Cap'n Web App" && AUTH0_API_NAME="Cap'n Web API" && AUTH0_API_IDENTIFIER="https://capnweb-api.$(date +%s).com" && brew tap auth0/auth0-cli && brew install auth0 && auth0 login --no-input && auth0 apis create --name "${AUTH0_API_NAME}" --identifier "${AUTH0_API_IDENTIFIER}" --scopes "read:profile,write:profile" --json > auth0-api-details.json && auth0 apps create -n "${AUTH0_APP_NAME}" -t spa -c http://localhost:3000 -l http://localhost:3000 -o http://localhost:3000 --json > auth0-app-details.json && CLIENT_ID=$(jq -r '.client_id' auth0-app-details.json) && DOMAIN=$(auth0 tenants list --json | jq -r '.[] | select(.active == true) | .name') && echo "AUTH0_DOMAIN=${DOMAIN}" > .env && echo "AUTH0_CLIENT_ID=${CLIENT_ID}" >> .env && echo "AUTH0_AUDIENCE=${AUTH0_API_IDENTIFIER}" >> .env && echo "PORT=3000" >> .env && echo "NODE_ENV=development" >> .env && rm auth0-app-details.json auth0-api-details.json && echo ".env file created with your Auth0 details:" && cat .env
          ```

          ```shellscript Windows theme={null}
          $AppName = "My Cap'n Web App"; $ApiName = "Cap'n Web API"; $ApiIdentifier = "https://capnweb-api.$((Get-Date).Ticks).com"; winget install Auth0.CLI; auth0 login --no-input; auth0 apis create --name "$ApiName" --identifier "$ApiIdentifier" --scopes "read:profile,write:profile" --json | Set-Content -Path auth0-api-details.json; auth0 apps create -n "$AppName" -t spa -c http://localhost:3000 -l http://localhost:3000 -o http://localhost:3000 --json | Set-Content -Path auth0-app-details.json; $ClientId = (Get-Content -Raw auth0-app-details.json | ConvertFrom-Json).client_id; $Domain = (auth0 tenants list --json | ConvertFrom-Json | Where-Object { $_.active -eq $true }).name; Set-Content -Path .env -Value "AUTH0_DOMAIN=$Domain"; Add-Content -Path .env -Value "AUTH0_CLIENT_ID=$ClientId"; Add-Content -Path .env -Value "AUTH0_AUDIENCE=$ApiIdentifier"; Add-Content -Path .env -Value "PORT=3000"; Add-Content -Path .env -Value "NODE_ENV=development"; Remove-Item auth0-app-details.json, auth0-api-details.json; Write-Output ".env file created with your Auth0 details:"; Get-Content .env
          ```
        </AuthCodeGroup>
      </Tab>

      <Tab title="Tableau de bord">
        Avant de commencer, créez un fichier `.env` à la racine de votre projet

        ```shellscript .env theme={null}
        AUTH0_DOMAIN=YOUR_AUTH0_APP_DOMAIN
        AUTH0_CLIENT_ID=YOUR_AUTH0_APP_CLIENT_ID
        AUTH0_AUDIENCE=https://capnweb-api.yourproject.com
        PORT=3000
        NODE_ENV=development
        ```

        1. Accédez au [Auth0 Dashboard](https://manage.auth0.com/dashboard/)
        2. Cliquez sur **Applications** > **Applications** > **Create Application**
        3. Dans la fenêtre contextuelle, saisissez un nom pour votre application, sélectionnez `Single Page Web Application` comme type d'application, puis cliquez sur **Create**
        4. Accédez à l’onglet **Settings** de la page des détails de l’Application
        5. Remplacez `YOUR_AUTH0_APP_DOMAIN` et `YOUR_AUTH0_APP_CLIENT_ID` dans le fichier `.env` par les valeurs **Domaine** et **ID client** de l’Auth0 Dashboard

        Enfin, dans l’onglet **Settings** de la page des détails de votre Application, configurez les URL suivantes :

        **URL de redirection autorisées :**

        ```
        http://localhost:3000
        ```

        **URL de déconnexion autorisées:**

        ```
        http://localhost:3000
        ```

        **Origines Web autorisées :**

        ```
        http://localhost:3000
        ```

        <Info>
          Les **URL de redirection autorisées** constituent une mesure de sécurité essentielle pour vous assurer que les utilisateurs sont renvoyés en toute sécurité vers votre application après l’authentification. Sans URL correspondante, le processus de connexion échouera et les utilisateurs seront bloqués par une page d’erreur Auth0 au lieu d’accéder à votre application.

          Les **URL de déconnexion autorisées** sont essentielles pour offrir une expérience utilisateur fluide lors de la déconnexion. Sans URL correspondante, les utilisateurs ne seront pas redirigés vers votre application après la déconnexion et resteront plutôt sur une page générique d’Auth0.

          La configuration **Allowed Web Origins** est essentielle pour l’authentification silencieuse. Sans cette configuration, les utilisateurs seront déconnectés lorsqu’ils actualiseront la page ou reviendront plus tard à votre application.
        </Info>

        **Obligatoire : créer une API Auth0**

        Vous devez créer une API Auth0 pour que la validation des jetons fonctionne :

        1. Dans l’Auth0 Dashboard, allez dans **APIs** > **Create API**
        2. Configurez les valeurs suivantes :
           * **Name**: `Cap'n Web API`
           * **Identifier**: `https://capnweb-api.yourproject.com` (utilisez votre propre identifiant unique)
           * **Signing Algorithm**: `RS256` (valeur par défaut)
        3. Cliquez sur **Create**
        4. Dans l’onglet **Scopes**, ajoutez les scopes suivants :
           * `read:profile` - Lire les données de profil de l’utilisateur
           * `write:profile` - Mettre à jour les données de profil de l’utilisateur
        5. Mettez à jour votre fichier `.env` avec l’identifiant de l’API sous la forme `AUTH0_AUDIENCE`

        <Warning>
          Si vous ne créez pas une API Auth0, vous obtiendrez une erreur « access\_denied » pendant la connexion. L’identifiant de l’API doit correspondre exactement à votre variable d’environnement `AUTH0_AUDIENCE`.
        </Warning>
      </Tab>
    </Tabs>
  </Step>

  <Step title="Créez le serveur" stepNumber={5}>
    Créez le serveur Web Cap'n avec l'intégration Auth0 :

    ```javascript server/index.js expandable lines theme={null}
    import { RpcTarget, newWebSocketRpcSession } from 'capnweb';
    import { WebSocketServer } from 'ws';
    import { ApiClient } from '@auth0/auth0-api-js';
    import http from 'http';
    import { readFileSync } from 'fs';
    import { dirname, join } from 'path';
    import { fileURLToPath } from 'url';
    import dotenv from 'dotenv';

    dotenv.config();

    const __dirname = dirname(fileURLToPath(import.meta.url));
    const userProfiles = new Map();

    // Auth0 configuration
    const AUTH0_DOMAIN = process.env.AUTH0_DOMAIN;
    const AUTH0_CLIENT_ID = process.env.AUTH0_CLIENT_ID;
    const AUTH0_AUDIENCE = process.env.AUTH0_AUDIENCE;
    const PORT = process.env.PORT || 3000;

    if (!AUTH0_DOMAIN || !AUTH0_CLIENT_ID || !AUTH0_AUDIENCE) {
      console.error('❌ Variables d'environnement Auth0 requises manquantes');
      if (!AUTH0_DOMAIN) console.error('   - AUTH0_DOMAIN est requis');
      if (!AUTH0_CLIENT_ID) console.error('   - AUTH0_CLIENT_ID est requis');
      if (!AUTH0_AUDIENCE) console.error('   - AUTH0_AUDIENCE est requis');
      process.exit(1);
    }

    // Initialize Auth0 API client for token verification
    const auth0ApiClient = new ApiClient({
      domain: AUTH0_DOMAIN,
      audience: AUTH0_AUDIENCE
    });

    async function verifyToken(token) {
      try {
        const payload = await auth0ApiClient.verifyAccessToken({
          accessToken: token
        });
        return payload;
      } catch (error) {
        throw new Error(`Échec de la vérification du jeton: ${error.message}`);
      }
    }

    // Profile Service - Cap'n Web RPC Target
    class ProfileService extends RpcTarget {
      async getProfile(accessToken) {
        const decoded = await verifyToken(accessToken);
        const userId = decoded.sub;
        const profile = userProfiles.get(userId) || { bio: '' };
        
        return {
          id: userId,
          email: decoded.email || 'Utilisateur inconnu',
          bio: profile.bio
        };
      }

      async updateProfile(accessToken, bio) {
        const decoded = await verifyToken(accessToken);
        const userId = decoded.sub;
        userProfiles.set(userId, { bio });
        
        return { success: true, message: 'Profil mis à jour avec succès' };
      }
    }

    // Create HTTP server
    const server = http.createServer(async (req, res) => {
      res.setHeader('Access-Control-Allow-Origin', '*');
      res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
      res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
      
      if (req.method === 'OPTIONS') {
        res.writeHead(200);
        res.end();
        return;
      }

      if (req.url === '/api/config') {
        const config = { 
          auth0: { 
            domain: AUTH0_DOMAIN, 
            clientId: AUTH0_CLIENT_ID,
            audience: AUTH0_AUDIENCE
          } 
        };
        res.setHeader('Content-Type', 'application/json');
        res.writeHead(200);
        res.end(JSON.stringify(config));
        return;
      }

      // Handle root path and Auth0 callback
      if (req.url === '/' || req.url === '/index.html' || req.url.startsWith('/?code=') || req.url.startsWith('/?error=')) {
        const html = readFileSync(join(__dirname, '../client/index.html'), 'utf8');
        res.setHeader('Content-Type', 'text/html');
        res.writeHead(200);
        res.end(html);
        return;
      }

      if (req.url === '/app.js') {
        const js = readFileSync(join(__dirname, '../client/app.js'), 'utf8');
        res.setHeader('Content-Type', 'application/javascript');
        res.writeHead(200);
        res.end(js);
        return;
      }

      // Serve Auth0 SPA JS SDK from node_modules
      if (req.url === '/@auth0/auth0-spa-js') {
        const modulePath = join(__dirname, '../node_modules/@auth0/auth0-spa-js/dist/auth0-spa-js.production.esm.js');
        const js = readFileSync(modulePath, 'utf8');
        res.setHeader('Content-Type', 'application/javascript');
        res.writeHead(200);
        res.end(js);
        return;
      }

      // Serve capnweb from node_modules
      if (req.url === '/capnweb') {
        const modulePath = join(__dirname, '../node_modules/capnweb/dist/index.js');
        const js = readFileSync(modulePath, 'utf8');
        res.setHeader('Content-Type', 'application/javascript');
        res.writeHead(200);
        res.end(js);
        return;
      }

      res.writeHead(404);
      res.end('Introuvable');
    });

    // WebSocket server for Cap'n Web RPC
    const wss = new WebSocketServer({ server });

    wss.on('connection', (ws, req) => {
      // Only handle RPC connections on /api path
      if (req.url === '/api') {
        console.log('🔗 Nouvelle connexion RPC Cap\'n Web');
        
        // Create a new ProfileService instance for this connection
        const profileService = new ProfileService();
        
        // Use capnweb's newWebSocketRpcSession to handle the connection
        newWebSocketRpcSession(ws, profileService);
      }
    });

    // Start server
    server.listen(PORT, () => {
      console.log(`🚀 Serveur Auth0 Cap'n Web démarré`);
      console.log(`📍 Serveur en cours d'exécution sur http://localhost:${PORT}`);
      console.log(`🔐 Domaine Auth0: ${AUTH0_DOMAIN}`);
      console.log(`🆔 ID client: ${AUTH0_CLIENT_ID.substring(0, 8)}...`);
      console.log(`🎯 Audience de l'API: ${AUTH0_AUDIENCE}`);
    });
    ```
  </Step>

  <Step title="Créer l’interface Client" stepNumber={6}>
    Créez les fichiers HTML et JavaScript de l'interface utilisateur :

    <AuthCodeGroup>
      ```html client/index.html expandable lines theme={null}
      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Démo Cap'n Web + Auth0</title>
          <script type="importmap">
          {
            "imports": {
              "@auth0/auth0-spa-js": "/@auth0/auth0-spa-js",
              "capnweb": "/capnweb"
            }
          }
          </script>
          <style>
              body {
                  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
                  max-width: 600px;
                  margin: 0 auto;
                  padding: 2rem;
                  line-height: 1.6;
              }
              .auth-section, .profile-section {
                  background: #f8f9fa;
                  border-radius: 8px;
                  padding: 2rem;
                  margin: 1rem 0;
              }
              .profile-section { display: none; }
              button {
                  background: #007bff;
                  color: white;
                  border: none;
                  padding: 0.75rem 1.5rem;
                  border-radius: 4px;
                  cursor: pointer;
                  font-size: 1rem;
                  margin: 0.5rem 0.5rem 0.5rem 0;
              }
              button:hover { background: #0056b3; }
              button.secondary { background: #6c757d; }
              button.secondary:hover { background: #545b62; }
              textarea {
                  width: 100%;
                  min-height: 100px;
                  margin: 1rem 0;
                  padding: 0.75rem;
                  border: 1px solid #ddd;
                  border-radius: 4px;
                  resize: vertical;
              }
              .status {
                  padding: 1rem;
                  margin: 1rem 0;
                  border-radius: 4px;
                  font-weight: 500;
              }
              .status.success { background: #d4edda; color: #155724; }
              .status.error { background: #f8d7da; color: #721c24; }
              .status.info { background: #d1ecf1; color: #0c5460; }
          </style>
      </head>
      <body>
          <h1>Démo de profil Cap'n Web + Auth0</h1>
          
          <div id="status" class="status info">Initialisation en cours...</div>
          
          <!-- Login Section -->
          <div id="authSection" class="auth-section">
              <h2>🔐 Bienvenue !</h2>
              <p>Cette démo présente l'authentification Auth0 avec Cap'n Web RPC pour la gestion de profil en temps réel.</p>
              <button id="loginBtn">Se connecter avec Auth0</button>
          </div>
          
          <!-- Profile Section -->
          <div id="profileSection" class="profile-section">
              <h2>👤 Gestion du profil</h2>
              <p><strong>Courriel :</strong> <span id="userEmail">Chargement en cours...</span></p>
              
              <label for="bioTextarea"><strong>Bio :</strong></label>
              <textarea id="bioTextarea" placeholder="Parlez-nous de vous..."></textarea>
              
              <div>
                  <button id="fetchBtn">🔄 Récupérer le profil</button>
                  <button id="saveBtn">💾 Enregistrer le profil</button>
                  <button id="logoutBtn" class="secondary">🚪 Se déconnecter</button>
              </div>
          </div>

          <script type="module" src="/app.js"></script>
      </body>
      </html>
      ```

      ```javascript client/app.js expandable lines theme={null}
      import { createAuth0Client } from '@auth0/auth0-spa-js';
      import { newWebSocketRpcSession } from 'capnweb';

      // Configuration Auth0
      let auth0Client = null;
      let profileApi = null;

      // Configuration Auth0 - Chargée dynamiquement du serveur
      let AUTH0_CONFIG = null;

      // Charger la configuration Auth0 du serveur
      async function loadConfig() {
        const response = await fetch('/api/config');
        const config = await response.json();
        
        AUTH0_CONFIG = {
          domain: config.auth0.domain,
          clientId: config.auth0.clientId,
          authorizationParams: {
            redirect_uri: window.location.origin,
            audience: config.auth0.audience,
            scope: 'openid profile email'
          },
          useRefreshTokens: true,
          cacheLocation: 'localstorage'
        };
        
        return AUTH0_CONFIG;
      }

      // Initialiser l'application
      async function initializeApp() {
        try {
          showStatus('Chargement de la configuration...', 'info');
          
          const config = await loadConfig();
          
          showStatus('Initialisation du client Auth0...', 'info');
          auth0Client = await createAuth0Client(config);
          
          const query = window.location.search;
          if (query.includes('code=') && query.includes('state=')) {
            showStatus('Traitement de la connexion...', 'info');
            await auth0Client.handleRedirectCallback();
            window.history.replaceState({}, document.title, window.location.pathname);
          }
          
          const isAuthenticated = await auth0Client.isAuthenticated();
          
          if (isAuthenticated) {
            showStatus('Connexion à Cap\'n Web RPC...', 'info');
            const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
            profileApi = newWebSocketRpcSession(`${protocol}//${window.location.host}/api`);
            
            await showProfileSection();
          } else {
            showAuthSection();
          }
          
          setupEventListeners();
          
        } catch (error) {
          console.error('Erreur d\'initialisation :', error);
          showStatus(`Échec de l'initialisation : ${error.message}`, 'error');
        }
      }

      // Fonctions d'authentification
      async function login() {
        try {
          showStatus('Redirection vers Auth0...', 'info');
          await auth0Client.loginWithRedirect();
        } catch (error) {
          showStatus(`Échec de la connexion : ${error.message}`, 'error');
        }
      }

      async function logout() {
        try {
          if (profileApi) {
            profileApi[Symbol.dispose]();
          }
          await auth0Client.logout({
            logoutParams: { returnTo: window.location.origin }
          });
        } catch (error) {
          showStatus(`Échec de la déconnexion : ${error.message}`, 'error');
        }
      }

      async function getAccessToken() {
        try {
          return await auth0Client.getTokenSilently({
            authorizationParams: {
              audience: AUTH0_CONFIG.authorizationParams.audience
            }
          });
        } catch (error) {
          if (error.error === 'consent_required' || error.error === 'interaction_required') {
            await auth0Client.loginWithRedirect({
              authorizationParams: {
                audience: AUTH0_CONFIG.authorizationParams.audience,
                scope: 'openid profile email',
                prompt: 'consent'
              }
            });
          }
          throw error;
        }
      }

      // Fonctions de gestion du profil
      async function fetchProfile() {
        try {
          showStatus('Récupération du profil...', 'info');
          
          const token = await getAccessToken();
          const user = await auth0Client.getUser();
          const profile = await profileApi.getProfile(token);
          
          document.getElementById('userEmail').textContent = user.email || profile.email || 'Aucun courriel disponible';
          document.getElementById('bioTextarea').value = profile.bio || '';
          
          showStatus('Profil chargé avec succès !', 'success');
        } catch (error) {
          showStatus(`Échec de la récupération du profil : ${error.message}`, 'error');
        }
      }

      async function saveProfile() {
        try {
          showStatus('Enregistrement du profil...', 'info');
          
          const token = await getAccessToken();
          const bio = document.getElementById('bioTextarea').value;
          
          const result = await profileApi.updateProfile(token, bio);
          
          showStatus(result.message || 'Profil enregistré avec succès !', 'success');
        } catch (error) {
          showStatus(`Échec de l'enregistrement du profil : ${error.message}`, 'error');
        }
      }

      // Fonctions auxiliaires pour l'interface utilisateur
      function showAuthSection() {
        document.getElementById('authSection').style.display = 'block';
        document.getElementById('profileSection').style.display = 'none';
        showStatus('Prêt pour la connexion', 'info');
      }

      async function showProfileSection() {
        document.getElementById('authSection').style.display = 'none';
        document.getElementById('profileSection').style.display = 'block';
        await fetchProfile();
      }

      function showStatus(message, type) {
        const statusEl = document.getElementById('status');
        statusEl.textContent = message;
        statusEl.className = `status ${type}`;
      }

      // Écouteurs d'événements
      function setupEventListeners() {
        document.getElementById('loginBtn').addEventListener('click', login);
        document.getElementById('logoutBtn').addEventListener('click', logout);
        document.getElementById('fetchBtn').addEventListener('click', fetchProfile);
        document.getElementById('saveBtn').addEventListener('click', saveProfile);
      }

      // Initialiser l'application lorsque le DOM est chargé
      document.addEventListener('DOMContentLoaded', initializeApp);
      ```
    </AuthCodeGroup>
  </Step>

  <Step title="Lancez votre application" stepNumber={7}>
    ```shellscript theme={null}
    npm run start
    ```
  </Step>
</Steps>

<Check>
  **Point de contrôle**

  Vous devriez maintenant avoir une page de connexion Auth0 entièrement fonctionnelle qui s’exécute sur votre [localhost](http://localhost:3000/).
</Check>

***

<div id="advanced-usage">
  ## Utilisation avancée
</div>

<Accordion title="Sécurité RPC côté serveur">
  Renforcez la sécurité en ajoutant une validation supplémentaire et un mécanisme de limitation de débit (`rate limiting`) à vos méthodes RPC :

  ```javascript server/profile-service.js theme={null}
  import rateLimit from 'express-rate-limit';

  class ProfileService extends RpcTarget {
    constructor() {
      super();
      this.rateLimiter = new Map(); // Mécanisme simple de limitation de débit en mémoire
    }

    async validateAndRateLimit(userId) {
      const now = Date.now();
      const userLimit = this.rateLimiter.get(userId) || { count: 0, resetTime: now + 60000 };
      
      if (now > userLimit.resetTime) {
        userLimit.count = 0;
        userLimit.resetTime = now + 60000;
      }
      
      if (userLimit.count >= 10) {
        throw new Error('Rate limit exceeded. Try again later.');
      }
      
      userLimit.count++;
      this.rateLimiter.set(userId, userLimit);
    }

    async getProfile(accessToken) {
      const decoded = await verifyToken(accessToken);
      await this.validateAndRateLimit(decoded.sub);
      
      const userId = decoded.sub;
      const profile = userProfiles.get(userId) || { bio: '' };
      
      return {
        id: userId,
        email: decoded.email || 'Unknown User',
        bio: profile.bio,
        lastUpdated: profile.lastUpdated || null
      };
    }
  }
  ```
</Accordion>

<Accordion title="Gestion des connexions WebSocket">
  Implémentez une gestion appropriée des connexions WebSocket avec reconnexion automatique :

  ```javascript client/connection-manager.js theme={null}
  import { newWebSocketRpcSession } from 'capnweb';

  class RpcConnectionManager {
    constructor(wsUrl, options = {}) {
      this.wsUrl = wsUrl;
      this.api = null;
      this.reconnectAttempts = 0;
      this.maxReconnectAttempts = options.maxReconnectAttempts || 5;
      this.reconnectDelay = options.reconnectDelay || 1000;
      this.isConnecting = false;
      this.onReconnect = options.onReconnect || (() => {});
    }

    async connect() {
      if (this.isConnecting) return this.api;
      this.isConnecting = true;

      try {
        // Fermer la connexion existante, le cas échéant
        if (this.api) {
          this.api[Symbol.dispose]();
        }

        // Créer une nouvelle session RPC WebSocket
        this.api = newWebSocketRpcSession(this.wsUrl);
        this.reconnectAttempts = 0;
        this.isConnecting = false;
        
        console.log('✅ RPC connection established');
        this.onReconnect(this.api);
        
        return this.api;
      } catch (error) {
        this.isConnecting = false;
        
        if (this.reconnectAttempts < this.maxReconnectAttempts) {
          this.reconnectAttempts++;
          console.log(`🔄 Reconnection attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts}`);
          
          await new Promise(resolve => 
            setTimeout(resolve, this.reconnectDelay * this.reconnectAttempts)
          );
          
          return this.connect();
        } else {
          throw new Error('Max reconnection attempts reached');
        }
      }
    }

    disconnect() {
      if (this.api) {
        this.api[Symbol.dispose]();
        this.api = null;
      }
      this.reconnectAttempts = 0;
    }

    getApi() {
      return this.api;
    }
  }

  // Utilisation dans app.js
  const connectionManager = new RpcConnectionManager(
    `${protocol}//${host}/api`,
    {
      maxReconnectAttempts: 5,
      reconnectDelay: 1000,
      onReconnect: async (api) => {
        // Actualiser l’interface utilisateur ou recharger les données après la reconnexion
        await displayProfile(api);
      }
    }
  );

  // Se connecter lorsque l’utilisateur est authentifié
  if (isAuthenticated) {
    await connectionManager.connect();
    profileApi = connectionManager.getApi();
  }
  ```
</Accordion>

<Accordion title="Intégration à une base de données">
  Remplacez le stockage en mémoire par une base de données pour un usage en production :

  ```javascript server/database.js theme={null}
  import { Pool } from 'pg';

  const pool = new Pool({
    connectionString: process.env.DATABASE_URL || 'postgresql://localhost/capnweb_auth0'
  });

  // Initialise le schéma de la base de données
  async function initializeDatabase() {
    await pool.query(`
      CREATE TABLE IF NOT EXISTS user_profiles (
        user_id VARCHAR(255) PRIMARY KEY,
        bio TEXT,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
      )
    `);
  }

  class DatabaseProfileService extends RpcTarget {
    async getProfile(accessToken) {
      const decoded = await verifyToken(accessToken);
      const result = await pool.query(
        'SELECT bio, updated_at FROM user_profiles WHERE user_id = $1',
        [decoded.sub]
      );
      
      return {
        id: decoded.sub,
        email: decoded.email,
        bio: result.rows[0]?.bio || '',
        lastUpdated: result.rows[0]?.updated_at || null
      };
    }
    
    async updateProfile(accessToken, bio) {
      const decoded = await verifyToken(accessToken);
      await pool.query(`
        INSERT INTO user_profiles (user_id, bio, updated_at) 
        VALUES ($1, $2, CURRENT_TIMESTAMP)
        ON CONFLICT (user_id) 
        DO UPDATE SET bio = $2, updated_at = CURRENT_TIMESTAMP
      `, [decoded.sub, bio]);
      
      return { success: true, message: 'Profile updated successfully' };
    }
  }

  export { initializeDatabase, DatabaseProfileService };
  ```
</Accordion>
