TokenHandler
createTokenHandler is the core of the library. Where many libraries mainly expose utility functions that you have to assemble yourself, the idea here is the opposite: you start by creating a handler that already carries the token management policy.
This handler brings together in one place:
- signing;
- encryption;
- token lifetime;
- claim management for header and payload;
In other words, the goal is to build a clear contract that you can reuse.
Created at startup
createTokenHandler is meant to be called when the application starts. The configuration is validated immediately, and an invalid configuration throws as soon as the handler is created.
Simple example
import { D, DPE } 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 verifiedToken = await tokenHandler.verify("receive-token");What happens here
When create runs, the handler adds standard claims such as iat and exp, then signs the content.
When verify runs, it decodes the token again, verifies the signature, then applies configuration checks such as expiration, issuer, subject, or audience.
create or createOrThrow
create returns a Left when token creation fails.createOrThrow does the same thing, but throws when creation fails. It is useful if you prefer an exception-based flow over an Either-based flow.
Parameters
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;
};The returned handler then exposes four methods:
createcreateOrThrowverifydecode
Example with custom shapes
import { asserts, D, DPE } 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.create(
{
userId: "42",
role: "admin",
},
{
header: {
kid: "main",
},
},
);
// send to client ...
const decodedToken = await tokenHandler.decode("receive-token");What happens here
customPayloadShape and customHeaderShape define what your application is allowed to put into the token.
Reserved JWT keys such as exp, iat, iss, sub, aud, typ, or alg remain managed by the handler itself.
Example with creators
import { D, DPE } 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.create(
{
userId: "1",
},
{
signer: {
secret: "my-secret",
},
cipher: {
privateKey: "private-key",
publicKey: "public-key",
},
},
);
// send to client ...
const verifiedToken = await tokenHandler.verify("receive-token", {
signer: {
secret: "my-secret",
},
cipher: {
privateKey: "private-key",
publicKey: "public-key",
},
});What happens here
When you pass a CreateSigner or a CreateCipher instead of an already configured instance, the parameters move to create, createOrThrow, verify, and decode.
This lets you create the handler only once, while injecting secrets, keys, or other required parameters later.
