Ir al contenido

Verificación de identidad

Tu write key pública identifica tu proyecto, pero no autentica a nadie: cualquiera puede abrir una consola del navegador y enviar eventos como distinct_id: "[email protected]". La verificación de identidad cierra ese agujero — tu backend firma un JWT de vida corta que prueba que este navegador realmente es este usuario, y Kilden lo verifica en cada evento identificado.

El tráfico anónimo no se ve afectado: no hay identidad que falsificar, así que los eventos anónimos nunca necesitan token.

  1. En la configuración del proyecto creas un identity secret (separado de tus write keys). Cada secret tiene un kid (key id), y varios pueden estar activos a la vez, así que la rotación no tiene ventana de corte.
  2. En el login, tu backend firma un JWT con ese secret — HS256, con el id del usuario en sub.
  3. El SDK envía el token con cada batch de eventos como Authorization: Bearer <token>.
  4. El pipeline lo verifica: firma contra un kid activo, exp válido, y sub igual al distinct_id del evento. El resultado se guarda en cada evento como verified.

Payload de claims:

{
"sub": "user_4821",
"iat": 1752192000,
"exp": 1752195600,
"traits": { "plan": "pro" }
}
  • subobligatorio. Debe ser igual al distinct_id que usará el navegador (lo que le pasas a identify()).
  • expobligatorio. Mantenlo corto (minutos a horas); el SDK lo refresca automáticamente.
  • kidobligatorio, en el header del JWT (no en el payload). Identifica qué identity secret del proyecto firmó el token.
  • traits — opcional. Traits firmados, aplicados con verificación del servidor.
  • Algoritmo: solo HS256. Los tokens firmados con cualquier otra cosa (incluido none) fallan la verificación.
import jwt from 'jsonwebtoken';
// Desde la configuración del proyecto → Verificación de identidad.
// El secret nunca sale de tu backend.
const KILDEN_IDENTITY_SECRET = process.env.KILDEN_IDENTITY_SECRET;
const KILDEN_IDENTITY_KID = process.env.KILDEN_IDENTITY_KID;
app.get('/api/kilden-token', requireAuth, (req, res) => {
const token = jwt.sign(
{
sub: req.user.id, // debe coincidir con el id que pasas a kilden.identify()
traits: { plan: req.user.plan },
},
KILDEN_IDENTITY_SECRET,
{ algorithm: 'HS256', expiresIn: '1h', keyid: KILDEN_IDENTITY_KID },
);
res.json({ token });
});
<?php
use Firebase\JWT\JWT;
// Desde la configuración del proyecto → Verificación de identidad.
$secret = getenv('KILDEN_IDENTITY_SECRET');
$kid = getenv('KILDEN_IDENTITY_KID');
function kildenIdentityToken(string $userId, array $traits = []): string
{
$now = time();
$payload = [
'sub' => $userId, // debe coincidir con el id pasado a kilden.identify()
'iat' => $now,
'exp' => $now + 3600,
'traits' => (object) $traits,
];
return JWT::encode($payload, $GLOBALS['secret'], 'HS256', $GLOBALS['kid']);
}

(Usa firebase/php-jwt; el cuarto argumento define el header kid.)

Deja que el SDK gestione el ciclo de vida del token — lo refresca alrededor de un minuto antes de exp y reintenta una vez ante un 401:

kilden.init('YOUR_WRITE_KEY', {
getIdentityToken: async () => {
const res = await fetch('/api/kilden-token');
if (!res.ok) return null;
return (await res.json()).token;
},
});
// después del login:
kilden.identify('user_4821');

Alternativas: pasar un token que ya tienes en el init (identityToken: '...'), por llamada (identify(id, traits, { token })), o de forma imperativa (setIdentityToken(token)). En el logout, reset() lo limpia.

El token viaja por batch, en el header Authorization: Bearer. Una excepción documentada: el flush de fin de página puede caer a sendBeacon, que no puede llevar headers, así que el token va en el body como identity_token — misma protección TLS, se chequea en segundo lugar.

La verificación se computa siempre — cada evento identificado recibe verified: true/false sin importar el modo. El modo solo gobierna qué pasa con las mutaciones de identidad no verificadas ($identify, $set, $set_once):

Modo Mutaciones de identidad no verificadas de usuarios identificados
off Se aplican normalmente
monitor Se aplican normalmente, pero cada una se cuenta para que veas exactamente qué bloquearía enforce
enforce No se aplican: no pueden crear personas, enlazar identidades ni escribir traits. La resolución de identidad para eventos identificados no verificados pasa a ser solo lectura — los mapeos existentes siguen atribuyendo, pero no se crea nada nuevo

Como monitor mide con precisión lo que enforce bloquearía, el rollout seguro es: instrumentar el token → observar cómo el contador de no verificados baja a cero → cambiar a enforce. El cambio no altera ningún dato, solo el comportamiento.

Dos reglas se cumplen en todos los modos:

  • Los eventos nunca se descartan. Una verificación fallida conserva el evento con verified: false; se excluye de los consumidores sensibles a la confianza (triggers de campañas, y lecturas del messenger cuando llegue la mensajería sobre canales identificados) pero permanece en tu analytics.
  • Los eventos anónimos siempre pasan. Su verified: false es una convención (no hay nada que verificar), no desconfianza. Los eventos server-side enviados con una secret key son verified: true — la secret key es la autenticación.

Los traits dentro del JWT se aplican como un $set verificado por el servidor y tienen prioridad sobre los traits sin firmar en el mismo evento. Úsalos para valores que el navegador no debería poder reclamar sobre sí mismo (plan, rol, estado de la cuenta). Para hechos que mueven dinero o mensajería, es preferible enviar el evento en sí server-side.

Crea un secret nuevo (nuevo kid) en la configuración del proyecto, despliega tu backend firmando con él, y luego desactiva el kid viejo. Tener varios secrets activos permite rotar sin downtime; los tokens firmados por un kid desactivado fallan la verificación de inmediato.