
Fastify Best Practices
Implement Fastify JWT login, route protection, and refresh-token patterns with copy-paste-ready TypeScript examples.
Overview
fastify-best-practices is an agent skill most often used in Build (also Ship security) that implements Fastify JWT authentication and protected-route patterns.
Install
npx skills add https://github.com/ilteoood/harness --skill fastify-best-practicesWhat is this skill?
- @fastify/jwt registration with sign options and request.jwtVerify guard
- authenticate onRequest hook pattern for protected routes
- JSON schema validation on login body (email + password)
- Refresh token flow patterns beyond access-token sign
- Metadata tags: auth, jwt, session, oauth, security, authorization
Adoption & trust: 1 installs on skills.sh; 2 GitHub stars; 3/3 security scanners passed (skills.sh audits); trending (+100% hot-view momentum).
What problem does it solve?
You are shipping a Fastify API but lack a consistent JWT login, verification, and protected-route setup your agent can apply without security mistakes.
Who is it for?
Solo builders creating TypeScript Fastify APIs who want JWT auth scaffolding before adding business routes.
Skip if: Greenfield teams choosing auth providers (Auth0, Clerk) with no self-hosted JWT, or non-Node HTTP frameworks.
When should I use this skill?
User asks for Fastify authentication, JWT, authorization, session, or OAuth patterns on a Node API.
What do I get? / Deliverables
Your Fastify app gains documented register/decorate auth hooks, schema-validated login, and protected route examples—then you add secrets handling and refresh-token storage in your own code.
- JWT plugin registration and authenticate decorator
- Login and protected route handler examples with JSON schema
Recommended Skills
Journey fit
Spans multiple journey phases - primary shelf plus alternate fits below.
Authentication routes and plugins are core backend work when you are building the API surface of a solo SaaS. The skill’s examples center on @fastify/jwt decorators, login POST, and protected GET handlers—classic backend API concerns.
Where it fits
Register @fastify/jwt and wire /login plus authenticate before adding CRUD routes.
Audit JWT expiry, refresh rotation, and 401 responses on protected handlers before production deploy.
How it compares
Opinionated Fastify JWT recipe skill—not a generic OpenAPI generator or a full OAuth provider integration.
Common Questions / FAQ
Who is fastify-best-practices for?
Indie and solo backend devs on Fastify who need JWT authentication and route guards without reading the entire Fastify ecosystem docs first.
When should I use fastify-best-practices?
In Build when adding auth plugins and login routes; in Ship/security when reviewing JWT expiry, refresh tokens, and 401 handling before launch.
Is fastify-best-practices safe to install?
Examples use environment-backed secrets and standard verify flows—review the Security Audits panel on this page and never commit JWT_SECRET or paste real credentials into prompts.
SKILL.md
READMESKILL.md - Fastify Best Practices
# Authentication and Authorization ## JWT Authentication with @fastify/jwt Use `@fastify/jwt` for JSON Web Token authentication: ```typescript import Fastify from 'fastify'; import fastifyJwt from '@fastify/jwt'; const app = Fastify(); app.register(fastifyJwt, { secret: process.env.JWT_SECRET, sign: { expiresIn: '1h', }, }); // Decorate request with authentication method app.decorate('authenticate', async function (request, reply) { try { await request.jwtVerify(); } catch (err) { reply.code(401).send({ error: 'Unauthorized' }); } }); // Login route app.post('/login', { schema: { body: { type: 'object', properties: { email: { type: 'string', format: 'email' }, password: { type: 'string' }, }, required: ['email', 'password'], }, }, }, async (request, reply) => { const { email, password } = request.body; const user = await validateCredentials(email, password); if (!user) { return reply.code(401).send({ error: 'Invalid credentials' }); } const token = app.jwt.sign({ id: user.id, email: user.email, role: user.role, }); return { token }; }); // Protected route app.get('/profile', { onRequest: [app.authenticate], }, async (request) => { return { user: request.user }; }); ``` ## Refresh Tokens Implement refresh token rotation: ```typescript import fastifyJwt from '@fastify/jwt'; import { randomBytes } from 'node:crypto'; app.register(fastifyJwt, { secret: process.env.JWT_SECRET, sign: { expiresIn: '15m', // Short-lived access tokens }, }); // Store refresh tokens (use Redis in production) const refreshTokens = new Map<string, { userId: string; expires: number }>(); app.post('/auth/login', async (request, reply) => { const { email, password } = request.body; const user = await validateCredentials(email, password); if (!user) { return reply.code(401).send({ error: 'Invalid credentials' }); } const accessToken = app.jwt.sign({ id: user.id, role: user.role }); const refreshToken = randomBytes(32).toString('hex'); refreshTokens.set(refreshToken, { userId: user.id, expires: Date.now() + 7 * 24 * 60 * 60 * 1000, // 7 days }); return { accessToken, refreshToken }; }); app.post('/auth/refresh', async (request, reply) => { const { refreshToken } = request.body; const stored = refreshTokens.get(refreshToken); if (!stored || stored.expires < Date.now()) { refreshTokens.delete(refreshToken); return reply.code(401).send({ error: 'Invalid refresh token' }); } // Delete old token (rotation) refreshTokens.delete(refreshToken); const user = await db.users.findById(stored.userId); const accessToken = app.jwt.sign({ id: user.id, role: user.role }); const newRefreshToken = randomBytes(32).toString('hex'); refreshTokens.set(newRefreshToken, { userId: user.id, expires: Date.now() + 7 * 24 * 60 * 60 * 1000, }); return { accessToken, refreshToken: newRefreshToken }; }); app.post('/auth/logout', async (request, reply) => { const { refreshToken } = request.body; refreshTokens.delete(refreshToken); return { success: true }; }); ``` ## Role-Based Access Control Implement RBAC with decorators: ```typescript type Role = 'admin' | 'user' | 'moderator'; // Create authorization decorator app.decorate('authorize', function (...allowedRoles: Role[]) { return async (request, reply) => { await request.jwtVerify(); const userRole = request.user.role as Role; if (!allowedRoles.includes(userRole)) { return reply.code(403).send({ error: 'Forbidden', message: `Role '${userRole}' is not authorized for this resource`, }); } }; }); // Admin only route app.get('/admin/users', { onRequest: [app.authorize('admin')], }, async (request) => { ret