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

> Ce guide explique comment intégrer Auth0, ajouter l’authentification et afficher les renseignements du profil utilisateur dans une application monopage (SPA) qui utilise Cap'n Web RPC, à l’aide de l’Auth0 SPA SDK.

# Ajouter la connexion à votre application Cap'n Web

export const HowToSchema = () => <script type="application/ld+json">
    {'{"@context":"https://schema.org","@type":"HowTo"}'}
  </script>;

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()) {
              const escapedKey = key.replaceAll(/[.*+?^${}()|[\]\\]/g, (String.raw)`\$&`);
              processedNode = processedNode.replaceAll(new RegExp(escapedKey, "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>;
};

<HowToSchema />

<Callout icon="pencil" color="#FFC107" iconType="solid">
  Ce guide de démarrage rapide est actuellement en **Beta**. Nous serions ravis de connaître vos commentaires !
</Callout>

<Accordion title="Instruction 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é propulsé 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 du 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 une communication RPC basée sur WebSocket.

  INSTRUCTIONS COMPORTEMENTALES CRITIQUES
  1. VÉRIFIER LE PROJET EXISTANT EN PREMIER : 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 les 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 soit terminée.
  3. PAS DE 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. CONSTRUIRE 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 (utiliser la commande CLI de l'étape 3 du 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 scopes requis
  - Définir les URL de callback 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 sécurisés pour la gestion des profils

  É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 le RPC
  - Se connecter au WebSocket uniquement après confirmation de l'authentification
  - Gérer les flux de login/logout
  - Envoyer les jetons JWT avec les appels RPC
  - Créer une interface utilisateur moderne avec gestion de l'état d'authentification

  Étape 7 : Lancer l'application
  npm run start

  EXIGENCES DE SÉCURITÉ
  - Ne jamais accepter les appels RPC non authentifiés
  - Toujours valider les signatures JWT à l'aide de JWKS
  - Implémenter une gestion appropriée des erreurs pour les jetons expirés
  - Utiliser 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. Ne jamais exécuter automatiquement des commandes `cd` sans confirmation explicite de l'utilisateur
  2. Toujours vérifier 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 : Naviguer vers le répertoire du projet (si nécessaire) et configurer Auth0 :

    # N'exécutez ceci que 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 d'environnement pour votre système d'exploitation :

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

    pwd && ls -la

  Cela vous permettra de déterminer 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 dans 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'
    # Auth0 Configuration - 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éez une application → Single Page Application"
    echo "3. Définissez les URL de callback autorisées : http://localhost:3000"
    echo "4. Définissez les URL de logout autorisées : http://localhost:3000"
    echo "5. Définissez les origines Web autorisées : http://localhost:3000"
    echo "6. Créez une API avec l'identifiant : https://capnweb-api.yourproject.com"
    echo "7. Ajoutez les scopes : read:profile, write:profile"
    echo "8. Mettez à jour le fichier .env avec votre domaine, votre ID client et l'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 l'intégralité du 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('❌ Missing required Auth0 environment variables');
      if (!AUTH0_DOMAIN) console.error('   - AUTH0_DOMAIN is required');
      if (!AUTH0_CLIENT_ID) console.error('   - AUTH0_CLIENT_ID is required');
      if (!AUTH0_AUDIENCE) console.error('   - AUTH0_AUDIENCE is required');
      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 des 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(`Token verification failed: ${error.message}`);
      }
    }

  4.2: Continue with RPC target implementation and HTTP server setup:

    // 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(`Authentication failed: ${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('Authentication required');

        const profile = userProfiles.get(user.sub) || {
          id: user.sub,
          name: user.name || 'Unknown User',
          email: user.email || 'No email provided',
          picture: user.picture || null,
          preferences: {},
          lastLogin: new Date().toISOString()
        };

        console.log('📋 Profile retrieved for user:', user.sub);
        return profile;
      }

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

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

        userProfiles.set(user.sub, updatedProfile);
        console.log('✅ Profile updated for user:', user.sub);
        return updatedProfile;
      }

      async getPublicData() {
        // Aucune authentification requise pour les méthodes publiques
        return {
          message: 'This is public data available to all users',
          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('Not Found');
      }
    });

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

    wss.on('connection', (ws) => {
      console.log('🔌 New WebSocket connection established');
      
      ws.on('message', async (message) => {
        try {
          const request = JSON.parse(message.toString());
          console.log('📨 Received RPC request:', request.method);
          
          // Extraire le jeton de la requête
          const token = request.token;
          let result;

          // Appeler la méthode appropriée selon 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(`Unknown method: ${request.method}`);
          }

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

      ws.on('close', () => {
        console.log('🔌 WebSocket connection closed');
      });

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

    server.listen(PORT, () => {
      console.log('🚀 Cap\'n Web Auth0 Server Started');
      console.log('📍 Server running on http://localhost:' + PORT);
      console.log('🔐 Auth0 Domain:', AUTH0_DOMAIN);
      console.log('🆔 Client ID:', AUTH0_CLIENT_ID.substring(0, 8) + '...');
      console.log('🎯 API Audience:', AUTH0_AUDIENCE);
      console.log('\n📋 Available RPC Methods:');
      console.log('   - getProfile (authenticated)');
      console.log('   - updateProfile (authenticated)');
      console.log('   - getPublicData (public)');
    });

  Step 5: Implement Server with JWT Validation
  AFTER Auth0 setup is complete, create the server with Cap'n Web RPC:

  5.1: Create the main server file (server/index.js)
  Import required modules and set up Auth0 token verification:

    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(`Token verification failed: ${error.message}`);
      }
    }

  5.2: Create ProfileService RpcTarget with authentication:

    // ProfileService - étend RpcTarget pour le RPC Cap'n Web
    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 || 'Unknown User',
          bio: profile.bio
        };
      }

      async updateProfile(accessToken, bio) {
        const decoded = await verifyToken(accessToken);
        const userId = decoded.sub;
        userProfiles.set(userId, { bio });
        
        return { success: true, message: 'Profile updated successfully' };
      }
    }

  5.3: Create HTTP server and WebSocket server:

    // 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 Auth0 SPA depuis 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 depuis 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('Not found');
    });

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

    wss.on('connection', (ws, req) => {
      // Traiter uniquement les connexions RPC sur le chemin /api
      if (req.url === '/api') {
        console.log('🔗 Nouvelle connexion Cap\'n Web RPC');
        
        // 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(`🚀 Cap'n Web Auth0 Server Started`);
      console.log(`📍 Server running on http://localhost:${PORT}`);
      console.log(`🔐 Auth0 Domain: ${AUTH0_DOMAIN}`);
      console.log(`🆔 Client ID: ${AUTH0_CLIENT_ID.substring(0, 8)}...`);
      console.log(`🎯 API Audience: ${AUTH0_AUDIENCE}`);
    });

  Step 6: Create Modern Client with Auth0 Integration
  6.1: Create the main HTML file (client/index.html) with import map:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Cap'n Web + Auth0 Demo</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="Auth0 Logo" class="logo" 
                 onerror="this.style.display='none'">
            
            <h1>Cap'n Web + Auth0</h1>
            <p class="subtitle">Secure WebSocket RPC with Authentication</p>
            
            <div id="auth-section">
                <button id="login-btn" class="button login">🔐 Login</button>
                <button id="logout-btn" class="button logout hidden">🚪 Logout</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>🔌 RPC Operations</h3>
                <button id="get-profile-btn" class="button rpc">📋 Get Profile</button>
                <button id="update-profile-btn" class="button rpc">✏️ Update Profile</button>
                <button id="get-public-btn" class="button rpc">🌐 Get Public Data</button>
            </div>

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

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

  6.2 : Créer l'application cliente JavaScript (client/app.js)
  Utilisez newWebSocketRpcSession de capnweb et le SDK Auth0 avec les importations de modules ES appropriées :

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

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

    // Charger la configuration Auth0 depuis le 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('Loading configuration...', 'info');
        const config = await loadConfig();
        
        showStatus('Initializing Auth0 client...', 'info');
        auth0Client = await createAuth0Client(config);
        
        // Gérer le callback de redirection
        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);
        }
        
        // Vérifier le statut d'authentification
        const isAuthenticated = await auth0Client.isAuthenticated();
        
        if (isAuthenticated) {
          // Se connecter au WebSocket uniquement si authentifié
          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');
      }
    }

    // Fonctions d'authentification
    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;
      }
    }

    // Gestion du profil via Cap'n Web RPC
    async function fetchProfile() {
      try {
        showStatus('Fetching profile...', 'info');
        const token = await getAccessToken();
        const user = await auth0Client.getUser();
        
        // Appeler la méthode RPC directement sur le stub profileApi
        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;
        
        // Appeler la méthode RPC directement sur le stub profileApi
        const result = await profileApi.updateProfile(token, bio);
        
        showStatus(result.message || 'Profile saved successfully!', 'success');
      } catch (error) {
        showStatus(`Failed to save profile: ${error.message}`, 'error');
      }
    }

    // Fonctions utilitaires de l'interface
    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}`;
    }

    // É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);
          
          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; // Déjà connecté
        }

        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 disconnected');
          this.showStatus('🔌 Disconnected from server', '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 not connected'));
          }

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

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

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

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

      async getProfile() {
        try {
          this.showStatus('🔄 Fetching profile...', 'info');
          const profile = await this.callRPC('getProfile');
          this.showRPCResult('Profile Data', profile);
        } catch (error) {
          console.error('❌ Get profile failed:', error);
          this.showStatus(`❌ Failed to get profile: ${error.message}`, 'error');
        }
      }

      async updateProfile() {
        try {
          this.showStatus('🔄 Updating profile...', 'info');
          const updates = {
            preferences: {
              theme: 'dark',
              notifications: true,
              lastAction: 'profile-update'
            }
          };
          
          const updatedProfile = await this.callRPC('updateProfile', updates);
          this.showRPCResult('Updated Profile', updatedProfile);
        } catch (error) {
          console.error('❌ Update profile failed:', error);
          this.showStatus(`❌ Failed to update profile: ${error.message}`, 'error');
        }
      }

      async getPublicData() {
        try {
          this.showStatus('🔄 Fetching public data...', 'info');
          const data = await this.callRPC('getPublicData', null, false);
          this.showRPCResult('Public Data', data);
        } catch (error) {
          console.error('❌ Get public data failed:', error);
          this.showStatus(`❌ Failed to get public data: ${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="Profile" class="profile-picture" onerror="this.src='${placeholderImage}'">
            <h3>${this.user.name || 'User'}</h3>
            <p><strong>Email:</strong> ${this.user.email || 'Not provided'}</p>
            <p><strong>User ID:</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 réussite 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('✅ RPC call completed successfully', 'success');
      }
    }

    // Initialiser l'application
    const app = new CapnWebAuth0Client();
    app.init().catch(error => {
    // Initialiser l'application lorsque le DOM est chargé
    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 « Login » pour s'authentifier avec Auth0
    - Consulter les informations de votre profil (l'adresse courriel sera affichée 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 logout
    - Tester les appels RPC (Get Profile, Update Profile, Get Public Data)
    - Vérifier l'état de la connexion WebSocket
    - Tester la fonctionnalité de logout

  EXIGENCES DE SÉCURITÉ ET MEILLEURES PRATIQUES
  - ✅ NE JAMAIS accepter des appels RPC non authentifiés pour les méthodes protégées
  - ✅ TOUJOURS valider les signatures JWT à l'aide des JWKS d'Auth0
  - ✅ Mettre en place une gestion complète des erreurs pour les jetons expirés ou invalides
  - ✅ 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 place des politiques CORS appropriées
  - ✅ Ajouter une limitation du débit des requêtes pour une utilisation en production
  - ✅ Assainir toutes les données avant leur stockage ou leur transmission

  CONSEILS DE DÉPANNAGE
  - Vérifier la console du navigateur pour les erreurs JavaScript
  - S'assurer que le fichier .env contient la configuration Auth0 correcte
  - Vérifier que les paramètres de l'application Auth0 correspondent à vos URL locales
  - Confirmer que les scopes de l'API sont correctement configurés 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">
  ## Pour commencer
</div>

Ce guide de démarrage rapide montre comment ajouter l’authentification Auth0 à une application Web Cap'n Web. Vous créerez une application Web moderne basée sur les RPC avec une fonction de connexion sécurisée à l’aide du framework Javascript de Cap'n Web et du Auth0 SPA SDK.

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

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

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

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

    Créez la structure des dossiers 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="Installer les dépendances" stepNumber={2}>
    Installez Cap'n Web et les dépendances essentielles :

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

    Installez les SDK Auth0 pour l’authentification et la vérification 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 du 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 Auth0 App" stepNumber={3}>
    Ensuite, vous devez créer une nouvelle application sur votre tenant Auth0 et ajouter les variables d'environnement à votre projet.

    Vous pouvez effectuer cette opération automatiquement en exécutant une commande CLI ou manuellement par le biais du Dashboard :

    <Tabs>
      <Tab title="CLI">
        Exécutez la commande shell suivante à la 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="Dashboard">
        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, entrez un nom pour votre application, sélectionnez `Single Page Web Application` comme type d’application, puis cliquez sur **Create**
        4. Passez à l’onglet **Settings** de la page Application Details
        5. Remplacez `YOUR_AUTH0_APP_DOMAIN` et `YOUR_AUTH0_APP_CLIENT_ID` dans le fichier `.env` par les valeurs **Domain** et **Client ID** du Dashboard

        Enfin, dans l’onglet **Settings** de votre page Application Details, configurez les URL suivantes :

        **Allowed Callback URLs:**

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

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

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

        **Origines Web autorisées :**

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

        <Info>
          **Allowed Callback URLs** constitue une mesure de sécurité essentielle pour garantir que les utilisateurs sont redirigés de façon sécuritaire vers votre application après l’authentication. Sans URL correspondante, le processus de login échouera et les utilisateurs verront une page d’erreur Auth0 au lieu d’accéder à votre application.

          **Allowed Logout URLs** est essentiel 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 le logout et resteront plutôt sur une page Auth0 générique.

          **Allowed Web Origins** est essentiel pour la silent authentication. Sans cela, les utilisateurs seront déconnectés lorsqu’ils actualiseront la page ou reviendront plus tard dans votre application.
        </Info>

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

        Vous devez créer une API Auth0 pour que la validation du token fonctionne :

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

        <Warning>
          Sans créer d’API Auth0, vous obtiendrez une erreur "access\_denied" pendant le login. L’identificateur de l’API doit correspondre exactement à votre variable d’environnement `AUTH0_AUDIENCE`.
        </Warning>
      </Tab>
    </Tabs>
  </Step>

  <Step title="Créer 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();

    // 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;

    if (!AUTH0_DOMAIN || !AUTH0_CLIENT_ID || !AUTH0_AUDIENCE) {
      console.error('❌ Missing required Auth0 environment variables');
      if (!AUTH0_DOMAIN) console.error('   - AUTH0_DOMAIN is required');
      if (!AUTH0_CLIENT_ID) console.error('   - AUTH0_CLIENT_ID is required');
      if (!AUTH0_AUDIENCE) console.error('   - AUTH0_AUDIENCE is required');
      process.exit(1);
    }

    // Initialisation du 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(`Token verification failed: ${error.message}`);
      }
    }

    // Service de profil - Cible RPC Cap'n Web
    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 || 'Unknown User',
          bio: profile.bio
        };
      }

      async updateProfile(accessToken, bio) {
        const decoded = await verifyToken(accessToken);
        const userId = decoded.sub;
        userProfiles.set(userId, { bio });
        
        return { success: true, message: 'Profile updated successfully' };
      }
    }

    // Création du serveur HTTP
    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;
      }

      // Gérer le chemin racine et le callback Auth0
      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;
      }

      // Servir le SDK JS SPA Auth0 depuis 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 depuis 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('Not found');
    });

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

    wss.on('connection', (ws, req) => {
      // Traiter uniquement les connexions RPC sur le chemin /api
      if (req.url === '/api') {
        console.log('🔗 New Cap\'n Web RPC connection');
        
        // 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émarrage du serveur
    server.listen(PORT, () => {
      console.log(`🚀 Cap'n Web Auth0 Server Started`);
      console.log(`📍 Server running on http://localhost:${PORT}`);
      console.log(`🔐 Auth0 Domain: ${AUTH0_DOMAIN}`);
      console.log(`🆔 Client ID: ${AUTH0_CLIENT_ID.substring(0, 8)}...`);
      console.log(`🎯 API Audience: ${AUTH0_AUDIENCE}`);
    });
    ```
  </Step>

  <Step title="Créer l’interface du client" stepNumber={6}>
    Créez les fichiers HTML et JavaScript du frontend :

    <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>Cap'n Web + Auth0 Demo</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>Cap'n Web + Auth0 Profile Demo</h1>
          
          <div id="status" class="status info">Initializing...</div>
          
          <!-- Section de connexion -->
          <div id="authSection" class="auth-section">
              <h2>🔐 Welcome!</h2>
              <p>This demo shows Auth0 authentication with Cap'n Web RPC for real-time profile management.</p>
              <button id="loginBtn">Login with Auth0</button>
          </div>
          
          <!-- Section de profil -->
          <div id="profileSection" class="profile-section">
              <h2>👤 Profile Management</h2>
              <p><strong>Email:</strong> <span id="userEmail">Loading...</span></p>
              
              <label for="bioTextarea"><strong>Bio:</strong></label>
              <textarea id="bioTextarea" placeholder="Tell us about yourself..."></textarea>
              
              <div>
                  <button id="fetchBtn">🔄 Fetch Profile</button>
                  <button id="saveBtn">💾 Save Profile</button>
                  <button id="logoutBtn" class="secondary">🚪 Logout</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 depuis le serveur
      let AUTH0_CONFIG = null;

      // Charger la configuration Auth0 depuis le 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('Loading configuration...', 'info');
          
          const config = await loadConfig();
          
          showStatus('Initializing Auth0 client...', 'info');
          auth0Client = await createAuth0Client(config);
          
          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);
          }
          
          const isAuthenticated = await auth0Client.isAuthenticated();
          
          if (isAuthenticated) {
            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');
        }
      }

      // Fonctions d'authentification
      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;
        }
      }

      // Fonctions de gestion du profil
      async function fetchProfile() {
        try {
          showStatus('Fetching profile...', 'info');
          
          const token = await getAccessToken();
          const user = await auth0Client.getUser();
          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;
          
          const result = await profileApi.updateProfile(token, bio);
          
          showStatus(result.message || 'Profile saved successfully!', 'success');
        } catch (error) {
          showStatus(`Failed to save profile: ${error.message}`, 'error');
        }
      }

      // Fonctions utilitaires de l'interface
      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}`;
      }

      // É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 une fois le DOM chargé
      document.addEventListener('DOMContentLoaded', initializeApp);
      ```
    </AuthCodeGroup>
  </Step>

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

<Check>
  **Vérification**

  Vous devriez maintenant avoir une page de connexion Auth0 entièrement fonctionnelle 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 une limitation du nombre de requêtes à 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(); // Limitation simple du nombre de requêtes 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">
  Assurez une gestion adéquate 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 {
        // Libérer la connexion existante, s'il y en a une
        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’UI ou recharger les données après la reconnexion
        await displayProfile(api);
      }
    }
  );

  // Se connecter après l’authentification
  if (isAuthenticated) {
    await connectionManager.connect();
    profileApi = connectionManager.getApi();
  }
  ```
</Accordion>

<Accordion title="Intégration de base de données">
  Remplacez le stockage en mémoire par une base de données 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'
  });

  // Initialiser 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: 'Profil mis à jour avec succès' };
    }
  }

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