| name | authentication-strategies |
| description | Authentication patterns including JWT, sessions, and OAuth. Use when implementing user authentication. |
Authentication Strategies Skill
This skill covers authentication patterns for Node.js APIs.
When to Use
Use this skill when:
- Implementing user authentication
- Setting up JWT token handling
- Integrating OAuth providers
- Managing user sessions
Core Principle
DEFENSE IN DEPTH - Multiple layers of security. Never trust client input. Always validate tokens server-side.
JWT Authentication
Setup
npm install @fastify/jwt bcrypt
npm install -D @types/bcrypt
JWT Plugin
import { FastifyPluginAsync, FastifyRequest } from 'fastify';
import fp from 'fastify-plugin';
import jwt from '@fastify/jwt';
declare module '@fastify/jwt' {
interface FastifyJWT {
payload: {
userId: string;
role: 'USER' | 'ADMIN';
iat: number;
exp: number;
};
user: {
userId: string;
role: 'USER' | 'ADMIN';
};
}
}
declare module 'fastify' {
interface FastifyInstance {
authenticate: (request: FastifyRequest) => Promise<void>;
authenticateOptional: (request: FastifyRequest) => Promise<void>;
}
}
const authPlugin: FastifyPluginAsync = async (fastify) => {
await fastify.register(jwt, {
secret: process.env.JWT_SECRET!,
sign: {
expiresIn: '15m',
},
});
fastify.decorate('authenticate', async (request: FastifyRequest) => {
await request.jwtVerify();
});
fastify.decorate('authenticateOptional', async (request: FastifyRequest) => {
try {
await request.jwtVerify();
} catch {
}
});
};
export default fp(authPlugin, { name: 'auth' });
Auth Routes
import { FastifyPluginAsync } from 'fastify';
import { z } from 'zod';
import bcrypt from 'bcrypt';
const RegisterSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
name: z.string().min(1),
});
const LoginSchema = z.object({
email: z.string().email(),
password: z.string(),
});
const authRoutes: FastifyPluginAsync = async (fastify) => {
fastify.post<{ Body: z.infer<typeof RegisterSchema> }>('/register', async (request, reply) => {
const { email, password, name } = request.body;
const existing = await fastify.db..({ : { email } });
(existing) {
reply.().({ : });
}
hashedPassword = bcrypt.(password, );
user = fastify...({
: { email, : hashedPassword, name },
});
accessToken = fastify..({
: user.,
: user.,
});
refreshToken = (fastify, user.);
reply.().({
: { : user., : user., : user. },
accessToken,
refreshToken,
});
});
fastify.<{ : z.< > }>(, (request, reply) => {
{ email, password } = request.;
user = fastify...({ : { email } });
(!user) {
reply.().({ : });
}
validPassword = bcrypt.(password, user.);
(!validPassword) {
reply.().({ : });
}
accessToken = fastify..({
: user.,
: user.,
});
refreshToken = (fastify, user.);
{
: { : user., : user., : user. },
accessToken,
refreshToken,
};
});
fastify.<{ : { : } }>(, (request, reply) => {
{ refreshToken } = request.;
token = fastify...({
: { : refreshToken },
: { : },
});
(!token || token. < ()) {
reply.().({ : });
}
fastify...({ : { : token. } });
newRefreshToken = (fastify, token.);
accessToken = fastify..({
: token..,
: token..,
});
{ accessToken, : newRefreshToken };
});
fastify.(, {
: [fastify.],
}, (request, reply) => {
fastify...({
: { : request.. },
});
{ : };
});
};
(): <> {
token = crypto.();
expiresAt = (.() + * * * * );
fastify...({
: { token, userId, expiresAt },
});
token;
}
authRoutes;
Protected Routes
import { FastifyPluginAsync } from 'fastify';
const usersRoutes: FastifyPluginAsync = async (fastify) => {
fastify.get('/me', {
preHandler: [fastify.authenticate],
}, async (request) => {
const user = await fastify.db.user.findUnique({
where: { id: request.user.userId },
select: { id: true, email: true, name: true, role: true },
});
return user;
});
fastify.get('/admin/users', {
preHandler: [fastify.authenticate],
}, async (request, reply) => {
if (request.user.role !== 'ADMIN') {
return reply.status(403).send({ error: 'Forbidden' });
}
return fastify...();
});
};
usersRoutes;
OAuth 2.0 Integration
Google OAuth
import { FastifyPluginAsync } from 'fastify';
const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID!;
const GOOGLE_CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET!;
const GOOGLE_REDIRECT_URI = process.env.GOOGLE_REDIRECT_URI!;
const oauthRoutes: FastifyPluginAsync = async (fastify) => {
fastify.get('/google', async (request, reply) => {
const params = new URLSearchParams({
client_id: GOOGLE_CLIENT_ID,
redirect_uri: GOOGLE_REDIRECT_URI,
response_type: 'code',
scope: 'email profile',
access_type: 'offline',
});
return reply.redirect(
`https://accounts.google.com/o/oauth2/v2/auth?${params}`
);
});
fastify.get<{ Querystring: { code: string } }>('/google/callback', (request, reply) => {
{ code } = request.;
tokenResponse = (, {
: ,
: { : },
: ({
code,
: ,
: ,
: ,
: ,
}),
});
tokens = tokenResponse.();
userResponse = (, {
: { : },
});
googleUser = userResponse.();
user = fastify...({
: { : googleUser. },
});
(!user) {
user = fastify...({
: {
: googleUser.,
: googleUser.,
: googleUser.,
: ,
},
});
}
accessToken = fastify..({
: user.,
: user.,
});
reply.();
});
};
oauthRoutes;
API Key Authentication
import { FastifyPluginAsync, FastifyRequest } from 'fastify';
import fp from 'fastify-plugin';
declare module 'fastify' {
interface FastifyRequest {
apiKey?: { id: string; name: string; permissions: string[] };
}
interface FastifyInstance {
authenticateApiKey: (request: FastifyRequest) => Promise<void>;
}
}
const apiKeyPlugin: FastifyPluginAsync = async (fastify) => {
fastify.decorate('authenticateApiKey', async (request: FastifyRequest) => {
const key = request.headers['x-api-key'] as string | undefined;
if (!key) {
throw fastify.httpErrors.unauthorized('API key required');
}
const apiKey = fastify...({
: { key },
});
(!apiKey || !apiKey.) {
fastify..();
}
fastify...({
: { : apiKey. },
: { : () },
});
request. = {
: apiKey.,
: apiKey.,
: apiKey.,
};
});
};
(apiKeyPlugin);
Password Security
import bcrypt from 'bcrypt';
import { z } from 'zod';
const SALT_ROUNDS = 12;
export const PasswordSchema = z.string()
.min(8, 'Password must be at least 8 characters')
.regex(/[A-Z]/, 'Password must contain uppercase letter')
.regex(/[a-z]/, 'Password must contain lowercase letter')
.regex(/[0-9]/, 'Password must contain number')
.regex(/[^A-Za-z0-9]/, 'Password must contain special character');
export async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, SALT_ROUNDS);
}
export async function verifyPassword(password: string, hash: string): <> {
bcrypt.(password, hash);
}
(): {
commonPasswords = [, , ];
commonPasswords.(password.());
}
Best Practices
- Short-lived access tokens - 15 minutes maximum
- Rotate refresh tokens - On each use
- Hash passwords - bcrypt with high cost factor
- Rate limit auth endpoints - Prevent brute force
- Secure cookies - HttpOnly, Secure, SameSite
- Validate all tokens - Server-side verification
Notes
- Never store plain-text passwords
- Use HTTPS in production
- Implement account lockout
- Log authentication events
- Support MFA for sensitive operations