logo
Développement
Rechercher
Intégration push (poignée de main d'identité / Push Handoff)

Intégration push (poignée de main d'identité / Push Handoff)

Scénario : l'utilisateur clique sur l'application d'extension du développeur depuis la page Espace de travail → Extensions, et la plateforme pousse l'identité de l'utilisateur vers la page d'atterrissage de l'application d'extension cible sous la forme ?wsa=<JWT>. Le développeur la consomme sur la page d'atterrissage, la vérifie côté backend, puis l'échange contre sa propre session.

Dans ce mode, le développeur n'a pas besoin d'appeler l'endpoint de signature de la plateforme — c'est le frontend de l'espace de travail qui l'appelle activement lorsque l'utilisateur clique. Le développeur se charge seulement de « recevoir et vérifier ».

Séquence de bout en bout

loading...
sequenceDiagram
    autonumber
    actor User as L'utilisateur accède à l'application d'extension
    participant GB as Plateforme GPTBots
    participant FE as Application d'extension-Page d'atterrissage
    participant BE as Application d'extension-Backend

    User->>GB: Clique sur l'icône de l'application (POST sign-token)
    GB->>GB: Vérifie que le cliqueur est membre de ce workspace
    GB->>GB: Signe le wsa avec la clé<br/>(JWT 5 minutes, aud=host de l'application d'extension)
    GB->>FE: Ouvre app_home_url?wsa=<JWT>

    Note over FE: consumeHandoff()<br/>lit le ?wsa=, POST vers le backend de l'application d'extension
    FE->>BE: POST /session/exchange { wsa }
    BE->>BE: verifyWsa() → identity
    BE->>BE: Établit sa propre session
    BE-->>FE: Retourne la session (Set-Cookie)

    Note over FE: history.replaceState efface le ?wsa=

Règles selon lesquelles la plateforme génère l'URL de redirection (le développeur n'a pas besoin de les implémenter) :

  • Fait une correspondance exacte de l'application d'extension cible selon app_home_url dans les informations enregistrées (toute URL non enregistrée est refusée à la signature, pour éviter la fuite d'identité).
  • Vérifie que le cliqueur est effectivement membre de cet espace de travail (workspace_id).
  • Si app_home_url contient déjà une query, le wsa est ajouté avec & ; la valeur du wsa est déjà encodée en URL, vous n'avez pas besoin de la décoder manuellement après l'avoir lue.

Frontend : consommer le wsa de la page d'atterrissage

Avec le SDK (recommandé)

import { consumeHandoff } from '@gptbots/workspace-extension-sdk'; try { const identity = await consumeHandoff({ exchangeUrl: '/session/exchange', // votre propre interface de vérification backend // search: location.search, // lit location.search par défaut // paramName: 'wsa', // nom de paramètre par défaut : wsa // strip: true, // efface par défaut le wsa de l'URL après succès }); bootYourApp(identity); } catch (e) { // pas de wsa (accès direct de l'utilisateur), ou échec de vérification côté backend showLoginOrError(e); }
                      
                      import { consumeHandoff } from '@gptbots/workspace-extension-sdk';

try {
  const identity = await consumeHandoff({
    exchangeUrl: '/session/exchange', // votre propre interface de vérification backend
    // search:   location.search,     // lit location.search par défaut
    // paramName: 'wsa',              // nom de paramètre par défaut : wsa
    // strip:     true,              // efface par défaut le wsa de l'URL après succès
  });
  bootYourApp(identity);
} catch (e) {
  // pas de wsa (accès direct de l'utilisateur), ou échec de vérification côté backend
  showLoginOrError(e);
}

                    
Ce bloc de code dans la fenêtre flottante

consumeHandoff fait quatre choses : ① lit le ?wsa= ② POST { wsa } vers exchangeUrlaprès succès, efface le ?wsa= via history.replaceState ④ retourne l'identity renvoyée par le backend.

Moment de l'effacement : le jeton n'est effacé de l'URL qu'après un échange réussi, afin qu'un échec transitoire puisse être réessayé par actualisation. C'est un jeton à usage unique valable 5 minutes ; si le développeur souhaite l'effacer immédiatement même en cas d'échec, il peut appeler manuellement stripHandoffToken() dans le catch.

Sans créer de session, seulement lire l'identité (receive-only)

import { readHandoffToken, stripHandoffToken } from '@gptbots/workspace-extension-sdk'; const token = readHandoffToken(); // fonction pure, retourne la chaîne JWT brute ou null if (token) { // il reste recommandé d'envoyer le token à votre backend pour verifyWsa avant de faire confiance à son contenu (ne parsez pas le JWT dans le frontend comme une identité de confiance) await fetch('/telemetry/visit', { method: 'POST', body: JSON.stringify({ wsa: token }) }); } stripHandoffToken(); // efface le wsa de l'URL dans tous les cas
                      
                      import { readHandoffToken, stripHandoffToken } from '@gptbots/workspace-extension-sdk';

const token = readHandoffToken();   // fonction pure, retourne la chaîne JWT brute ou null
if (token) {
  // il reste recommandé d'envoyer le token à votre backend pour verifyWsa avant de faire confiance à son contenu (ne parsez pas le JWT dans le frontend comme une identité de confiance)
  await fetch('/telemetry/visit', { method: 'POST', body: JSON.stringify({ wsa: token }) });
}
stripHandoffToken();                // efface le wsa de l'URL dans tous les cas

                    
Ce bloc de code dans la fenêtre flottante

⚠️ Ne décodez pas le JWT dans le frontend pour le traiter comme une identité de confiance. La signature du JWT ne peut être vérifiée qu'avec la clé, et la clé n'existe que dans le backend. Le parsing frontend ne peut servir que d'espace réservé « non sécurisé » pour l'affichage ; toute décision d'autorisation doit reposer sur le résultat de verifyWsa côté backend.


Backend : vérifier le wsa

3.1 Avec le SDK (recommandé)

import { verifyWsa, WsaVerificationError } from '@gptbots/workspace-extension-verify'; app.post('/session/exchange', (req, res) => { try { const identity = verifyWsa(req.body.wsa, { secret: process.env.EXTENSION_APP_SECRET, // clé per-app Tier2 ou clé partagée Tier1 audience: 'app.example.com', // host de l'application d'extension, doit être égal à aud // issuer: 'gptbots-workspace', // par défaut // leewaySeconds: 30, // tolérance de dérive d'horloge, 30s par défaut // algorithms: ['HS256'], // par défaut }); // Isolation multi-tenant : rattachez la requête à identity.workspaceId const sid = createSession(identity); res.cookie('sid', sid, { httpOnly: true, secure: true, sameSite: 'lax' }); res.json(identity); } catch (e) { const code = e instanceof WsaVerificationError ? e.code : 'Error'; res.status(401).json({ code }); } });
                      
                      import { verifyWsa, WsaVerificationError } from '@gptbots/workspace-extension-verify';

app.post('/session/exchange', (req, res) => {
  try {
    const identity = verifyWsa(req.body.wsa, {
      secret: process.env.EXTENSION_APP_SECRET, // clé per-app Tier2 ou clé partagée Tier1
      audience: 'app.example.com',              // host de l'application d'extension, doit être égal à aud
      // issuer: 'gptbots-workspace',          // par défaut
      // leewaySeconds: 30,                    // tolérance de dérive d'horloge, 30s par défaut
      // algorithms: ['HS256'],                // par défaut
    });

    // Isolation multi-tenant : rattachez la requête à identity.workspaceId
    const sid = createSession(identity);
    res.cookie('sid', sid, { httpOnly: true, secure: true, sameSite: 'lax' });
    res.json(identity);
  } catch (e) {
    const code = e instanceof WsaVerificationError ? e.code : 'Error';
    res.status(401).json({ code });
  }
});

                    
Ce bloc de code dans la fenêtre flottante

Sans le SDK

Le wsa est un JWT HS256 standard, vérifiable par n'importe quelle bibliothèque JWT dans n'importe quel langage. Voir les exemples Java / Node / Python dans 05-Vérification du jeton et sécurité §3. Que vous utilisiez le SDK ou non, les quatre vérifications signature / iss / aud / exp sont obligatoires.

Enregistrer l'application d'extension (obtenir la clé)

Le OWNER/ADMIN de l'espace de travail : Espace de travail → Gestion de l'espace → Applications d'extension → Ajouter, puis remplissez :

Champ Description
Nom de l'application Nom d'affichage, ≤ 12 caractères chinois recommandé pour éviter la troncature
Icône de l'application Icône carrée, ≥ 128×128 recommandé
URL d'accès de l'application Votre app_home_url, qui sert de clé unique ; la signature applique une correspondance stricte sur la chaîne complète
Mode d'authentification Choisissez workspace_account (nécessite la transmission de l'identité)

Après validation, l'App Secret est affiché une seule fois en clair ; copiez-le et sauvegardez-le immédiatement (une fois fermé, il ne peut être que renouvelé).

Liste de vérification de la page d'atterrissage

  • Après réception du ?wsa=, envoyez-le d'abord en POST au service backend de l'application d'extension pour vérification, avant de faire confiance à son contenu
  • Une fois la vérification réussie, effacez immédiatement le wsa via history.replaceState (déjà fait par défaut par consumeHandoff)
  • Échangez contre une session propre ; les XHR/fetch/img suivants ne transmettent plus le wsa
  • Gérez la branche « accès direct de l'utilisateur, sans wsa » (redirection vers la connexion ou anonyme)
  • En cas d'échec de vérification, affichez un message lisible selon le WsaVerificationError.code

Détails de sécurité et vérification multi-langage : Vérification du jeton et sécurité. Pour passer à un « bouton de connexion dans l'application » : Connexion pull.