Skip to content

TokenHandler

createTokenHandler est le coeur de la librairie. Là où beaucoup de bibliothèques exposent surtout des fonctions utilitaires à assembler, ici l'idée est inverse : on commence par créer un handler qui porte déjà la politique de gestion du token.

Ce handler regroupe au même endroit :

  • la signature ;
  • le chiffrement ;
  • la durée de vie du token ;
  • la gestions des claims (header/payload) ;

Autrement dit, le but est de construire un contrat clair que l'on peut réutiliser.

Création au démarrage

createTokenHandler est pensé pour être appelé au démarrage d'une l'application. La configuration est validée immédiatement, et une configuration invalide provoque un throw dès la création du handler.

Exemple simple

ts
import { 
asyncPipe
,
D
,
DPE
,
E
} from "@duplojs/utils";
import {
Signer
,
createTokenHandler
} from "@duplojs/json-web-token";
const
tokenHandler
=
createTokenHandler
({
maxAge
:
D
.
createTime
(15, "minute"),
signer
:
Signer
.
createHS256
({
secret
: "my-secret" }),
issuer
: "my-app",
audience
: ["web"],
customPayloadShape
: {
userId
:
DPE
.
string
(),
}, }); const
token
= await
tokenHandler
.
createOrThrow
({
userId
: "1",
}); // send to client ... const
result
= await
asyncPipe
(
"receive-token",
tokenHandler
.
verify
,
E
.
whenIsRight
(
({
payload
}) => {
const
userId
=
payload
.
userId
;
}, ), );

Ce qui se passe ici

Au moment du createOrThrow, le handler ajoute lui-même les claims standards comme iat et exp, puis signe le contenu.
Au moment du verify, il redécode le token, vérifie la signature, puis applique les contrôles de configuration comme l'expiration, l'issuer, le subject ou l'audience.

verify retourne un résultat either. En cas de succès, l'information est token-verified et la valeur contient le header et le payload décodés.

Pourquoi createOrThrow existe ?

En pratique, on recommande createOrThrow.
Si la création d'un token échoue, c'est généralement un problème d'implémentation ou de configuration côté serveur, pas un scénario métier attendu. Dans ce cas, lever une erreur immédiatement est un comportement sain.

À l'inverse, verify n'a pas de variante orThrow : un échec de vérification peut être parfaitement normal (signature invalide, token expiré, claims incohérents, etc.). Ce n'est pas forcément une erreur d'implémentation, donc ce cas reste géré comme un résultat métier.

Paramètres

typescript
interface TokenHandlerParams {
	maxAge: D.TheTime;
	signer: Signer<string> | CreateSigner<string, unknown>;
	cipher?: Cipher<string> | CreateCipher<string, unknown>;
	issuer?: string;
	subject?: string;
	audience?: string | string[];
	now?: () => D.TheDate;
	customPayloadShape: DP.DataParserObjectShape;
	customHeaderShape?: DP.DataParserObjectShape;
};

Le handler retourné expose ensuite quatre méthodes :

  • create : crée un token et retourne token-created ou une erreur de création.
  • createOrThrow : crée directement une chaîne de token, ou throw si la création échoue.
  • verify : vérifie la signature et les claims configurés, puis retourne token-verified ou une erreur de vérification.
  • decode : lit le header et le payload sans vérifier la signature ni les claims, puis retourne token-decoded ou une erreur de décodage.

Exemple avec custom shapes

ts
import { 
D
,
DPE
,
E
,
unwrap
} from "@duplojs/utils";
import {
Signer
,
createTokenHandler
} from "@duplojs/json-web-token";
const
tokenHandler
=
createTokenHandler
({
maxAge
:
D
.
createTime
(1, "hour"),
signer
:
Signer
.
createHS256
({
secret
: "my-secret" }),
issuer
: "admin-app",
customPayloadShape
: {
userId
:
DPE
.
string
(),
role
:
DPE
.
literal
("admin"),
},
customHeaderShape
: {
kid
:
DPE
.
string
().
optional
(),
}, }); const
token
= await
tokenHandler
.
createOrThrow
(
{
userId
: "42",
role
: "admin",
}, {
header
: {
kid
: "main",
}, }, ); // send to client ... const
decodedTokenResult
= await
tokenHandler
.
decode
("receive-token");
if (
E
.
isRight
(
decodedTokenResult
)) {
const
decodedToken
=
unwrap
(
decodedTokenResult
);
const
userId
=
decodedToken
.
payload
.
userId
;
}

Ce qui se passe ici

customPayloadShape et customHeaderShape définissent ce que ton application a le droit de mettre dans le token.
Les clés réservées du JWT, comme exp, iat, iss, sub, aud, typ ou alg, restent gérées par le handler lui-même.

decode lit uniquement le contenu du token. C'est utile pour inspecter, mais ce n'est pas la méthode à utiliser pour faire confiance à un token reçu.

Exemple avec des "creators"

ts
import { 
D
,
DPE
,
E
,
unwrap
} from "@duplojs/utils";
import {
Cipher
,
Signer
,
createTokenHandler
} from "@duplojs/json-web-token";
const
tokenHandler
=
createTokenHandler
({
maxAge
:
D
.
createTime
(10, "minute"),
signer
:
Signer
.
createHS256
,
cipher
:
Cipher
.
createRSAOAEP
,
customPayloadShape
: {
userId
:
DPE
.
string
(),
}, }); const
token
= await
tokenHandler
.
createOrThrow
(
{
userId
: "1",
}, {
signer
: {
secret
: "my-secret",
},
cipher
: {
privateKey
: "private-key",
publicKey
: "public-key",
}, }, ); // send to client ... const
verifiedTokenResult
= await
tokenHandler
.
verify
("receive-token", {
signer
: {
secret
: "my-secret",
},
cipher
: {
privateKey
: "private-key",
publicKey
: "public-key",
}, }); if (
E
.
isRight
(
verifiedTokenResult
)) {
const
verifiedToken
=
unwrap
(
verifiedTokenResult
);
const
userId
=
verifiedToken
.
payload
.
userId
;
}

Ce qui se passe ici

Quand tu passes un CreateSigner ou un CreateCipher au lieu d'une instance déjà configurée, les paramètres sont déplacés vers create, createOrThrow, verify et decode.
Cela permet de créer le handler une seule fois, tout en injectant plus tard les secrets, les clés ou d'autres paramètres nécessaires.

Le résultat de succès reste le même : create retourne token-created, decode retourne token-decoded et verify retourne token-verified.

Diffusé sous licence MIT.