Fastify JWT Auth Guard Decorator

Protect Fastify routes with the JWT plugin via preHandler guards, verifying tokens once and exposing typed claims to handlers.

TypescriptNodeAuth Jwt

Code

snippet
import Fastify from 'fastify';
import jwt from '@fastify/jwt';

interface TokenClaims {
  sub: string;
  role: 'admin' | 'editor' | 'viewer';
}

declare module 'fastify' {
  interface FastifyRequest {
    user: TokenClaims;
  }
}

const app = Fastify({ logger: true });

await app.register(jwt, {
  secret: process.env.JWT_SECRET ?? 'dev-secret-change-me',
  sign: { expiresIn: '15m' },
});

function requireRole(...roles: TokenClaims['role'][]) {
  return async (req: Fastify.FastifyRequest, reply: Fastify.FastifyReply) => {
    try {
      await req.jwtVerify<TokenClaims>();
    } catch {
      return reply.code(401).send({ error: 'invalid or expired token' });
    }
    if (!roles.includes(req.user.role)) {
      return reply.code(403).send({ error: 'insufficient role' });
    }
  };
}

app.get('/me', { preHandler: requireRole('viewer', 'editor', 'admin') },
  async (req) => ({ userId: req.user.sub, role: req.user.role }),
);

app.post('/admin/purge', { preHandler: requireRole('admin') },
  async (req, reply) => reply.code(202).send({ purged: true, by: req.user.sub }),
);

app.inject({
  method: 'GET',
  url: '/me',
  headers: { authorization: 'Bearer ' + app.jwt.sign({ sub: 'u-1', role: 'viewer' }) },
}).then((res) => console.log('status:', res.statusCode));