Aller au contenu

Vérification d'identité

Votre write key publique identifie votre projet, mais elle n’authentifie personne : n’importe qui peut ouvrir une console de navigateur et envoyer des événements avec distinct_id: "[email protected]". La vérification d’identité ferme cette brèche — votre backend signe un JWT à courte durée de vie prouvant que ce navigateur est bien cet utilisateur, et Kilden le vérifie sur chaque événement identifié.

Le trafic anonyme n’est pas concerné : il n’y a aucune identité à forger, donc les événements anonymes n’ont jamais besoin de token.

  1. Dans les réglages du projet, vous créez un secret d’identité (distinct de vos write keys). Chaque secret a un kid (key id), et plusieurs peuvent être actifs en même temps, si bien que la rotation se fait sans fenêtre de bascule.
  2. À la connexion, votre backend signe un JWT avec ce secret — HS256, avec l’id utilisateur dans sub.
  3. Le SDK envoie le token avec chaque batch d’événements dans Authorization: Bearer <token>.
  4. Le pipeline le vérifie : signature contre un kid actif, exp valide, et sub égal au distinct_id de l’événement. Le résultat est stocké sur chaque événement dans verified.

Payload des claims :

{
"sub": "user_4821",
"iat": 1752192000,
"exp": 1752195600,
"traits": { "plan": "pro" }
}
  • subobligatoire. Doit être égal au distinct_id que le navigateur utilisera (ce que vous passez à identify()).
  • expobligatoire. Gardez-le court (de quelques minutes à quelques heures) ; le SDK le rafraîchit automatiquement.
  • kidobligatoire, dans le header du JWT (pas dans le payload). Identifie le secret d’identité du projet qui a signé le token.
  • traits — optionnel. Traits signés, appliqués comme vérifiés côté serveur.
  • Algorithme : HS256 uniquement. Les tokens signés avec autre chose (y compris none) échouent à la vérification.
import jwt from 'jsonwebtoken';
// Depuis les réglages du projet → Vérification d'identité.
// Le secret ne quitte jamais votre 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, // doit correspondre à l'id passé à 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;
// Depuis les réglages du projet → Vérification d'identité.
$secret = getenv('KILDEN_IDENTITY_SECRET');
$kid = getenv('KILDEN_IDENTITY_KID');
function kildenIdentityToken(string $userId, array $traits = []): string
{
$now = time();
$payload = [
'sub' => $userId, // doit correspondre à l'id passé à kilden.identify()
'iat' => $now,
'exp' => $now + 3600,
'traits' => (object) $traits,
];
return JWT::encode($payload, $GLOBALS['secret'], 'HS256', $GLOBALS['kid']);
}

(Utilise firebase/php-jwt ; le quatrième argument définit le header kid.)

Laissez le SDK gérer le cycle de vie du token — il le rafraîchit environ une minute avant exp et réessaie une fois sur 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;
},
});
// après la connexion :
kilden.identify('user_4821');

Alternatives : passer un token que vous avez déjà à l’init (identityToken: '...'), par appel (identify(id, traits, { token })), ou impérativement (setIdentityToken(token)). À la déconnexion, reset() l’efface.

Le token voyage par batch, dans le header Authorization: Bearer. Une exception documentée : le flush de fin de page peut se rabattre sur sendBeacon, qui ne peut pas transporter de headers, donc le token part dans le corps comme identity_token — même protection TLS, vérifié en second.

La vérification est toujours calculée — chaque événement identifié reçoit verified: true/false, quel que soit le mode. Le mode ne gouverne que le sort des mutations d’identité non vérifiées ($identify, $set, $set_once) :

Mode Mutations d’identité non vérifiées d’utilisateurs identifiés
off Appliquées normalement
monitor Appliquées normalement, mais chacune est comptée pour que vous voyiez exactement ce que enforce bloquerait
enforce Non appliquées : elles ne peuvent ni créer de personnes, ni lier des identités, ni écrire des traits. La résolution d’identité des événements identifiés non vérifiés devient une simple consultation — les mappings existants attribuent toujours, mais rien de nouveau n’est créé

Comme monitor mesure précisément ce que enforce bloquerait, le déploiement sûr est : instrumenter le token → regarder le compteur d’événements non vérifiés tomber à zéro → passer en enforce. La bascule ne change aucune donnée, seulement le comportement.

Deux règles tiennent dans tous les modes :

  • Les événements ne sont jamais jetés. Une vérification en échec conserve l’événement avec verified: false ; il est exclu des consommateurs sensibles à la confiance (déclencheurs de campagne, et les lectures du messenger quand la messagerie sur canaux identifiés sera livrée), mais reste dans vos analytics.
  • Les événements anonymes passent toujours. Leur verified: false est une convention (il n’y a rien à vérifier), pas de la méfiance. Les événements côté serveur envoyés avec une clé secrète sont verified: true — la clé secrète est l’authentification.

Les traits inclus dans le JWT sont appliqués comme un $set vérifié côté serveur et priment sur les traits non signés du même événement. Utilisez-les pour les valeurs que le navigateur ne devrait pas pouvoir revendiquer sur lui-même (plan, rôle, statut du compte). Pour les faits qui pilotent de l’argent ou de la messagerie, préférez envoyer l’événement lui-même côté serveur.

Créez un nouveau secret (nouveau kid) dans les réglages du projet, déployez votre backend en signant avec, puis désactivez l’ancien kid. Plusieurs secrets actifs signifient une rotation sans interruption ; les tokens signés par un kid désactivé échouent immédiatement à la vérification.