Integración push (handshake de identidad / Push Handoff)
Escenario: el usuario hace clic en la app de extensión del desarrollador en la página Espacio de trabajo → Extensiones, y la plataforma envía la identidad del usuario mediante ?wsa=<JWT> a la página de aterrizaje de la app de extensión de destino. El desarrollador la consume en la página de aterrizaje y, tras verificarla en el backend, la canjea por su propia sesión.
En este modo, el desarrollador no necesita invocar el endpoint de firma de la plataforma: ese lo invoca el frontend del espacio de trabajo de forma proactiva cuando el usuario hace clic. El desarrollador solo se encarga de «recibir y verificar».
Secuencia de extremo a extremo
sequenceDiagram
autonumber
actor User as El usuario accede a la app de extensión
participant GB as Plataforma GPTBots
participant FE as App de extensión - página de aterrizaje
participant BE as App de extensión - backend
User->>GB: Hace clic en el icono de la app (POST sign-token)
GB->>GB: Verifica que quien hace clic es miembro de ese workspace
GB->>GB: Firma el wsa con la clave<br/>(JWT de 5 minutos, aud=host de la app de extensión)
GB->>FE: Abre app_home_url?wsa=<JWT>
Note over FE: consumeHandoff()<br/>lee ?wsa=, hace POST al backend de la app de extensión
FE->>BE: POST /session/exchange { wsa }
BE->>BE: verifyWsa() → identity
BE->>BE: Establece la sesión propia
BE-->>FE: Devuelve la sesión (Set-Cookie)
Note over FE: history.replaceState elimina el ?wsa=
Reglas de cómo la plataforma genera la URL de redirección (el desarrollador no necesita implementarlo):
- A partir de la información de registro, hace una coincidencia exacta de la app de extensión de destino según
app_home_url(las URL no registradas se rechazan siempre en la firma, para prevenir la filtración de identidad). - Verifica que quien hace clic es realmente miembro de ese espacio de trabajo (
workspace_id). - Si el
app_home_urlya lleva una query, concatena elwsacon&; el valor delwsaya está codificado en URL, y tras leerlo no necesitas decodificarlo manualmente.
Frontend: consumir el wsa de la página de aterrizaje
Usar el SDK (recomendado)
import { consumeHandoff } from '@gptbots/workspace-extension-sdk';
try {
const identity = await consumeHandoff({
exchangeUrl: '/session/exchange', // tu propio endpoint de verificación en el backend
// search: location.search, // por defecto lee location.search
// paramName: 'wsa', // nombre del parámetro por defecto: wsa
// strip: true, // por defecto elimina el wsa de la URL tras el éxito
});
bootYourApp(identity);
} catch (e) {
// no hay wsa (el usuario accedió directamente), o la verificación del backend falló
showLoginOrError(e);
}
consumeHandoff hace cuatro cosas: ① lee el ?wsa= ② hace POST { wsa } a exchangeUrl ③ tras el éxito elimina el ?wsa= con history.replaceState ④ devuelve el identity que retornó el backend.
Momento de la eliminación: el token solo se elimina de la URL tras un intercambio exitoso, de modo que un fallo transitorio puntual pueda reintentarse actualizando la página. Es un token de un solo uso válido durante 5 minutos; si el desarrollador desea eliminarlo de inmediato también en caso de fallo, puede invocar manualmente
stripHandoffToken()después delcatch.
No crear sesión, solo leer la identidad (receive-only)
import { readHandoffToken, stripHandoffToken } from '@gptbots/workspace-extension-sdk';
const token = readHandoffToken(); // función pura, devuelve la cadena JWT original o null
if (token) {
// aun así se recomienda enviar el token a tu backend y ejecutar verifyWsa antes de confiar en su contenido (no analices el JWT en el frontend como identidad de confianza)
await fetch('/telemetry/visit', { method: 'POST', body: JSON.stringify({ wsa: token }) });
}
stripHandoffToken(); // pase lo que pase, elimina el wsa de la URL
⚠️ No decodifiques el JWT en el frontend y lo tomes como identidad de confianza. La firma del JWT solo puede verificarse con la clave, y la clave solo está en el backend. El análisis en el frontend solo sirve como marcador de posición «no seguro» para mostrar datos; cualquier decisión de autorización debe basarse en el resultado de
verifyWsadel backend.
Backend: verificar el wsa
3.1 Usar el SDK (recomendado)
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, // clave per-app de Tier2 o clave compartida de Tier1
audience: 'app.example.com', // host de la app de extensión, debe ser igual al aud
// issuer: 'gptbots-workspace', // por defecto
// leewaySeconds: 30, // tolerancia de deriva del reloj, por defecto 30s
// algorithms: ['HS256'], // por defecto
});
// Aislamiento multitenant: asigna la petición al nombre de 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 });
}
});
Sin usar el SDK
El wsa es un JWT HS256 estándar, y cualquier librería JWT de cualquier lenguaje puede verificarlo. Los ejemplos de Java / Node / Python están en 05-Verificación de token y seguridad §3. Uses o no el SDK, los cuatro elementos firma / iss / aud / exp deben verificarse siempre.
Registrar la app de extensión (obtener la clave)
El OWNER/ADMIN del espacio de trabajo: Espacio de trabajo → Gestión del espacio → Apps de extensión → Añadir, y completa:
| Campo | Descripción |
|---|---|
| Nombre de la app | Nombre visible, se recomienda ≤ 12 caracteres para evitar truncamientos |
| Icono de la app | Icono cuadrado, se recomienda ≥ 128×128 |
| URL de entrada de la app | Tu app_home_url, que actúa como clave única; al firmar se compara de forma estricta por la cadena completa |
| Modo de autenticación | Selecciona workspace_account (requiere transmitir la identidad) |
Tras enviar, el sistema muestra el App Secret en texto plano una única vez; cópialo y guárdalo de inmediato (una vez cerrado, solo puedes rotarlo).
Lista de comprobación de la página de aterrizaje
- Tras recibir el
?wsa=, primero haz POST al servicio de backend de la app de extensión para verificarlo, y luego confía en su contenido - Si la verificación es correcta, elimina el
wsade inmediato conhistory.replaceState(consumeHandoffya lo hace por defecto) - Canjéalo por la sesión propia, y las peticiones XHR/fetch/img posteriores ya no transmitirán el
wsa - Gestiona la rama de «el usuario accede directamente, sin
wsa» (guíalo al inicio de sesión o al modo anónimo) - Ante un fallo de verificación, ofrece un mensaje legible según
WsaVerificationError.code
Detalles de seguridad y verificación en varios lenguajes: Verificación de token y seguridad. ¿Quieres cambiarlo por un «botón de inicio de sesión dentro de la app»?: Inicio de sesión pull.
