05 · Verificación de token y seguridad
Ya sea en modo push o pull, al final el desarrollador siempre obtiene un wsa (JWT HS256). Este documento es de lectura obligatoria para todos los integradores: una verificación laxa = una identidad falsificable.
Lista de verificación obligatoria
Tras obtener el wsa, verifica en el servicio de backend de la app de extensión en el siguiente orden (verifyWsa lo incorpora todo internamente):
- Firma: verifica con HS256 usando la clave distribuida/registrada por la plataforma. Si falla → rechazar.
exp: el tiempo actual ≤exp(incluyendo un pequeño leeway). Elexpdebe existir: un token sinexpdebe rechazarse directamente (de lo contrario, equivale a que nunca expira).iss: debe ser igual agptbots-workspace.aud: debe ser igual al host de la app de extensión de destino. Esta es la línea de defensa clave para evitar que «el token sea robado para atacar otra app», y no puede omitirse.iat/nbf(si existen): no deben estar en el futuro (más allá del leeway), para evitar que un token con «el momento de emisión fijado en un futuro lejano» pueda usarse durante mucho tiempo.- Segmentación por
workspace_id: si la extensión implementa aislamiento entre varios espacios de trabajo, asigna la petición a eseworkspace_idy prohíbe el acceso entre tenants. - Elimina de inmediato el
wsa/codede la URL tras el aterrizaje: conhistory.replaceState, para evitar que el usuario comparta el token al copiar la URL. - Establece la sesión propia: canjéalo por la sesión/cookie de la propia app de extensión, y las peticiones posteriores no deben depender del
wsa, que expira en 5 minutos.
El
verifyWsadel SDK oficial exige obligatoriamente que exista elexp, verifica la deriva futura deiat/nbf, y realiza una comparación de firma en tiempo constante y una lista blanca de algoritmos (por defecto soloHS256, para evitar ataques de confusión dealg). Al usarlo, cumples automáticamente los puntos 1–5.
Se recomienda verificar con el SDK
import { verifyWsa, WsaVerificationError } from '@gptbots/workspace-extension-verify';
try {
const id = verifyWsa(wsa, {
secret: process.env.EXTENSION_APP_SECRET,
audience: 'app.example.com',
});
// id: { accountId, role, workspaceId, username?, email?, avatar?, appName?, issuedAt?, expiresAt? }
} catch (e) {
if (e instanceof WsaVerificationError) {
// e.code: InvalidToken | InvalidSignature | Expired | NotYetValid
// | WrongIssuer | WrongAudience | MissingClaim | UnsupportedAlgorithm
}
}
Las opciones completas y los códigos de error están en Referencia de la API del SDK.
Verificación sin SDK (en varios lenguajes)
El wsa es un JWT HS256 estándar, y cualquier librería JWT puede verificarlo. Asegúrate de activar explícitamente la verificación de iss / aud y de fijar algorithms=['HS256'].
Java (auth0 java-jwt)
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SHARED_SECRET))
.withIssuer("gptbots-workspace")
.withAudience("app.example.com")
.acceptLeeway(30) // tolera 30s de deriva del reloj
.build();
DecodedJWT jwt = verifier.verify(wsaParam);
String userId = jwt.getSubject();
String workspaceId = jwt.getClaim("workspace_id").asString();
String role = jwt.getClaim("role").asString();
String username = jwt.getClaim("username").asString(); // puede ser null
String email = jwt.getClaim("email").asString(); // puede ser null
Node.js (jsonwebtoken)
const jwt = require('jsonwebtoken');
const payload = jwt.verify(wsaParam, SHARED_SECRET, {
algorithms: ['HS256'],
issuer: 'gptbots-workspace',
audience: 'app.example.com',
clockTolerance: 30,
});
const { sub: userId, workspace_id, role, username, email, avatar } = payload;
Python (PyJWT)
import jwt
payload = jwt.decode(
wsa_param,
SHARED_SECRET,
algorithms=["HS256"],
issuer="gptbots-workspace",
audience="app.example.com",
leeway=30,
)
user_id = payload["sub"]
workspace_id = payload["workspace_id"]
role = payload["role"]
Recordatorio sobre la codificación de la clave: HS256 usa directamente la cadena de la clave como clave HMAC según sus bytes UTF-8. La clave per-app de Tier 2 tiene la forma
wext_+64 hex; pásala como clave tal cual, en formato de cadena (no vuelvas a decodificarla en hex), para mantener la coherencia con el lado de firma de la plataforma.
Requisitos de seguridad (a implementar punto por punto)
- La clave es todo el modelo de seguridad. Actualmente HS256 es una clave simétrica: una vez filtrada, cualquiera puede falsificar la identidad de cualquier usuario del espacio de trabajo y atacar la app de extensión integrada. Colócala solo en el backend (variables de entorno/KMS); nunca la escribas en el código del frontend, en el repositorio de git, en los logs ni en la configuración del cliente.
- La superficie de exposición del JWT en la URL (solo en modo push): la query string queda registrada en el historial del navegador, en el access log del servidor web, en los logs de caché del CDN y en los registros del Referer. Aunque quede registrada, si se obtiene dentro de los 5 minutos, todavía puede aprovecharse. Elimínalo de inmediato con
history.replaceStatetras el aterrizaje (consumeHandoffya lo hace por defecto). El modo pull (M-Auth) por naturaleza no coloca elwsaen la URL, lo que lo hace más seguro. - No transmitas el
wsaa subrecursos. Una vez canjeado por la sesión propia, las peticiones XHR/fetch/img posteriores no deben volver a llevar elwsaoriginal; de lo contrario aparecería en el Referer de cada subrecurso. - Sincronización del reloj. HS256 evalúa el
expde forma estricta, por lo que el reloj del servidor debe estar sincronizado por NTP; elleewayde 30s del ejemplo tolera pequeñas derivas, pero no lo amplíes al orden de minutos. - El
rolesolo sirve como mapeo de permisos por defecto. Solo refleja el rol del usuario en ese espacio de trabajo, no representa sus permisos dentro de tu app; tu app mantiene su propio modelo de permisos. - Aislamiento multitenant. Usa siempre el
workspace_idcomo clave de aislamiento de datos, para evitar que un usuario del espacio de trabajo A lea los datos del B. - Emergencia de rotación/revocación de la clave: en la gestión del espacio, «rota la clave» de la app correspondiente, lo que generará una clave nueva que se mostrará una única vez; a continuación, reconfigura tu backend con la nueva clave.
5. Frontera de confianza del frontend (importante)
- El frontend solo actúa como marcador de posición para mostrar datos, no toma decisiones de autorización. El frontend puede decodificar en base64 el payload del JWT, pero sin la clave no puede verificar la firma: cualquiera podría falsificar un payload «que parezca correcto».
- Todas las decisiones sobre «quién es esta persona, si puede hacer algo o no» deben basarse en el resultado de
verifyWsadel backend de la app de extensión. - Por eso, la estrategia estándar es: el frontend entrega el
wsa/codeal backend de la app de extensión → el backend verifica → el backend crea la sesión → el frontend solo reconoce esa sesión.
Siguiente paso: consulta Referencia de la API del SDK para ver la API completa; ante cualquier problema, consulta Preguntas frecuentes y solución de problemas.
