Identificar usuarios
Usa Jotly('identify', …) para decirle a Jotly quién es el usuario con sesión. Una vez identificado, sus conversaciones, votos e ideas quedan a su nombre, tu equipo ve su contexto y puede entrar a tu portal público sin volver a iniciar sesión.
Hay cuatro mecanismos y todos usan el mismo secreto de identidad de tu espacio:
- Token JWT (recomendado):
identify({ token }), firmado en tu servidor. Resultadosigned. - Firma HMAC del id:
identify({ id, signature }). Resultadosigned. - Sin firma:
identify({ id }). Resultadounsigned; útil para empezar, no para datos sensibles. - SSO del portal (plan Pro): el botón Entrar del portal lleva a tu app y regresa con un JWT.
Identificación básica
Llama a identify después de init, cuando tu producto ya sabe quién está conectado:
Jotly('identify', { id: 'user_123', email: 'ana@empresa.com', name: 'Ana López',});Sólo id es obligatorio; el resto es opcional, aunque casi siempre vale la pena mandar email. Usa el id de usuario de tu propia app: el identificador estable que le asignas en tu sistema, no un id de sesión ni un valor que cambie entre inicios de sesión.
Además del id puedes mandar su avatar, empresa, gasto mensual y atributos libres que tu equipo verá junto a cada conversación:
Jotly('identify', { id: 'user_123', // obligatorio: tu id de usuario email: 'ana@empresa.com', name: 'Ana López', avatar: 'https://…/ana.png', company: 'Empresa SA', monthlySpend: 49, attributes: { plan: 'pro', pais: 'MX' }, signature: 'HMAC_DEL_ID', // ver "Firmar el id con HMAC-SHA256"});En la mayoría de las apps, identifica al usuario cuando:
- inicia sesión
- tu app recupera una sesión que ya existía
- cambia su perfil y quieres que Jotly refleje los datos nuevos
Identidad firmada con un token JWT
Sin firma, cualquiera podría escribir otro id en la consola del navegador. Con un token, Jotly comprueba que la identidad viene de tu servidor. Genera un JWT HS256 con el secreto de Ajustes → Inicio de sesión y pásalo en token. identify devuelve una promesa con el resultado:
// El token lo genera tu servidor y lo entrega a la página.const { viewer, identityTrust, warnings } = await Jotly('identify', { token: tokenDeTuServidor,}); console.log(identityTrust); // 'signed'Claims del token
| Claim | Obligatorio | Qué es |
|---|---|---|
sub | Sí | Tu id de usuario (texto). |
iss | Sí | El slug o el id de tu espacio en Jotly. |
aud | Sí | "jotly" |
iat | Sí | Segundos Unix en que lo firmaste. |
exp | Sí | Vencimiento; máximo 1 hora después de iat. Toleramos 60 s de diferencia de reloj. |
email | No | Correo del usuario. |
firstName, lastName | No | Nombre y apellido. |
avatar | No | URL https de su foto. |
traits | No | Atributos con valores de texto, número, booleano o null. |
companies | No | Lista de { id, name?, domain?, traits? }. |
Los datos dentro del token están verificados. Si además mandas campos sueltos (email, name…), son información extra sin verificar y el token gana cuando no coinciden.
import crypto from "node:crypto"; // En TU servidor, nunca en el navegador.const SECRETO = process.env.JOTLY_SECRET;const ESPACIO = "tu-espacio"; // slug de tu espacio en Jotlyconst b64 = (v) => Buffer.from(JSON.stringify(v)).toString("base64url"); export function tokenJotly(user) { const ahora = Math.floor(Date.now() / 1000); const datos = b64({ alg: "HS256", typ: "JWT" }) + "." + b64({ sub: String(user.id), // tu id de usuario iss: ESPACIO, // slug o id del espacio aud: "jotly", iat: ahora, exp: ahora + 10 * 60, // máximo 1 hora después de iat email: user.email, firstName: user.firstName, lastName: user.lastName, avatar: user.avatarUrl, traits: { plan: user.plan }, companies: [{ id: String(user.companyId), name: user.companyName }], }); const firma = crypto.createHmac("sha256", SECRETO).update(datos).digest("base64url"); return datos + "." + firma;}import { SignJWT } from "jose"; const secreto = new TextEncoder().encode(process.env.JOTLY_SECRET); export async function tokenJotly(user) { return new SignJWT({ email: user.email, firstName: user.firstName, lastName: user.lastName }) .setProtectedHeader({ alg: "HS256" }) .setSubject(String(user.id)) .setIssuer("tu-espacio") .setAudience("jotly") .setIssuedAt() .setExpirationTime("10m") .sign(secreto);}Un token inválido, vencido o conaudoissincorrectos nunca se acepta como identidad sin firma:identifyrechaza la promesa conIDENTIFY_FAILEDy la API responde401con un código comoTOKEN_EXPIRED,TOKEN_SIGNATURE,TOKEN_AUDIENCEoTOKEN_ISSUER. Genera un token nuevo en cada carga de página.
Firmar el id con HMAC-SHA256
La alternativa al token es firmar sólo el id: HMAC-SHA256(id, secreto) en hexadecimal, en el mismo identify. El resultado también es signed, pero sólo el id queda verificado, no el resto de los campos.
Jotly('identify', { id: 'user_123', email: 'ana@empresa.com', signature: 'HMAC_DEL_ID', // calculada en tu servidor});import crypto from "node:crypto"; // En TU servidor, nunca en el navegador.const SECRETO = process.env.JOTLY_SECRET; export function firmaJotly(userId) { return crypto.createHmac("sha256", SECRETO).update(String(userId)).digest("hex");}import hmac, hashlib, os SECRETO = os.environ["JOTLY_SECRET"].encode() def firma_jotly(user_id: str) -> str: return hmac.new(SECRETO, str(user_id).encode(), hashlib.sha256).hexdigest()<?phpfunction firma_jotly(string $userId): string { return hash_hmac('sha256', $userId, getenv('JOTLY_SECRET'));}Si mandas una firma que no coincide, Jotly rechaza la identificación. Revisa que firmes exactamente el mismo id, como texto, que pasas en identify.Exigir identidad firmada
En Ajustes → Inicio de sesión puedes activar Exigir identidad firmada. Con eso encendido, cualquier identify sin token o firma válidos se rechaza con SIGNED_IDENTITY_REQUIRED, y el inicio automático al portal sólo acepta tokens JWT. Un usuario que ya se verificó una vez tampoco puede volver a entrar sin firma.
Entrar al portal público
Tu portal de feedback tiene tres formas de iniciar sesión, en Ajustes → Inicio de sesión → Inicio de sesión del portal:
- Enlace por correo (predeterminado): el usuario escribe su correo y recibe un enlace mágico y un código de 6 dígitos. Ambos sirven una sola vez y vencen en 15 minutos. Escribir el correo de otra persona no da acceso a nada.
- SSO de tu app (plan Pro): el botón Entrar lleva a tu app, que firma un JWT.
- Deshabilitado (plan Pro): no hay botón Entrar. Participan los invitados, si los permites, y quien llega con inicio automático.
Con Publicar como invitado activo, el visitante también puede publicar, votar y comentar sin cuenta. Cada invitado es una identidad nueva que sólo ve y edita lo suyo mientras dure su sesión en ese navegador.
SSO del portal
El flujo, como en cualquier SSO con redirección:
- El usuario pulsa Entrar y Jotly lo manda a tu URL de inicio de sesión con
?state=…. Elstatees de un solo uso y vence en 15 minutos. - Tu app lo autentica como siempre (o lo reconoce si ya tenía sesión).
- Tu servidor firma un JWT con los mismos claims de arriba (aquí
iates opcional; sugerimos que dure de 5 a 15 minutos) y redirige a/api/portal/tu-espacio/sso?jwt=…&state=…con el mismostate. La URL exacta está en Ajustes.
import express from "express";import { tokenJotly } from "./jotly.js"; // la función de arriba const app = express(); // URL de inicio de sesión que pegaste en Jotly: https://tuapp.com/jotly/ssoapp.get("/jotly/sso", requireLogin, (req, res) => { const state = String(req.query.state ?? ""); const jwt = tokenJotly(req.user); const regreso = new URL("https://jotly.si/api/portal/tu-espacio/sso"); regreso.searchParams.set("jwt", jwt); regreso.searchParams.set("state", state); // el mismo state, sin cambios res.redirect(regreso.toString());});Si algo falla, el usuario regresa al portal con ?sso_error= y uno de estos valores: invalid_state (el state no existe, ya se usó o venció), invalid_token (firma, aud, iss o vencimiento del JWT) o auth_failed (el SSO está apagado o la cuenta no puede participar).
Inicio automático desde tu app
Si tu app tiene el widget con un usuario identificado, cualquier enlace de tu app que apunte a tu portal sale con ?clientToken= y el usuario llega ya con su sesión. No tienes que hacer nada más: el widget reescribe el enlace justo cuando el usuario lo abre.
// clientToken = base64 de este JSON (no es secreto: la confianza viene del token de dentro){ "version": 2, "id": "tu-espacio", "token": "eyJhbGciOiJIUzI1NiJ9…" // o "user": { "id", "email", "name", "signature" }}Con Exigir identidad firmada sólo se aceptan los clientToken que traen un JWT. El inicio automático no aplica a las cuentas de tu equipo.
Cerrar sesión
Al salir de tu app, olvida al usuario para que el siguiente que use el navegador no vea su historial:
// Cuando el usuario cierra sesión en tu appJotly('logout');Si todavía no instalas el widget, empieza por Instalación.
Ver también Widget de feedback en tu app