Custom JWT Claims (buildTokenPayload)
By default, node-auth embeds these standard claims in every JWT:
{
"sub": "userId",
"email": "user@example.com",
"role": "user",
"loginProvider": "local",
"isEmailVerified": true,
"isTotpEnabled": false,
"iat": 1700000000,
"exp": 1700000900
}
Use buildTokenPayload in AuthConfig to merge additional custom claims — tenant IDs, permissions, plan tier, feature flags, or any project-specific data — into every issued token pair.
Flow
Configuration
import { AuthConfigurator, BaseUser } from '@nik2208/node-auth';
// Extend BaseUser with your project-specific fields
interface AppUser extends BaseUser {
tenantId?: string;
plan?: 'free' | 'pro' | 'enterprise';
}
const auth = new AuthConfigurator({
accessTokenSecret: process.env.ACCESS_TOKEN_SECRET!,
refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET!,
accessTokenExpiresIn: '15m',
refreshTokenExpiresIn: '7d',
buildTokenPayload: (user: AppUser) => ({
tenantId: user.tenantId,
plan: user.plan,
}),
}, userStore);
The returned object is merged on top of the standard claims. You cannot override sub, email, role, iat, or exp.
Reading custom claims server-side
After auth.middleware() runs, custom claims are available on req.user:
app.get('/api/data', auth.middleware(), (req, res) => {
const { sub, email, tenantId, plan } = req.user as any;
res.json({ tenantId, plan });
});
Reading custom claims client-side (NestJS example)
// @CurrentUser() injects req.user which includes all JWT claims
@Get('profile')
@UseGuards(JwtAuthGuard)
getProfile(@CurrentUser() user: { sub: string; email: string; tenantId: string; plan: string }) {
return user;
}
Async buildTokenPayload (loading RBAC from DB)
buildTokenPayload can be async. Use it to load permissions at token-issue time:
buildTokenPayload: async (user) => {
const permissions = await rbacStore.getPermissionsForUser(user.id);
const roles = await rbacStore.getRolesForUser(user.id);
return { permissions, roles };
},
Token size
JWT tokens are sent in every request (as cookies). Keep custom claims small — embed IDs and flags, not full objects. A token exceeding ~4 KB may be rejected by some cookie jars.
Common patterns
Multi-tenant ID
buildTokenPayload: (user) => ({ tenantId: user.tenantId }),
Role + permissions
buildTokenPayload: async (user) => ({
permissions: await rbacStore.getPermissionsForUser(user.id),
}),
Feature flags
buildTokenPayload: (user) => ({
features: {
analytics: user.plan === 'pro' || user.plan === 'enterprise',
export: user.plan === 'enterprise',
},
}),