A simple, secure JWT authentication example built with Fastify and TypeScript, demonstrating best practices for token-based authentication with refresh token support.
- JWT Authentication - Secure access and refresh token implementation
- HttpOnly Cookies - Refresh tokens stored securely in HttpOnly cookies
- Token Refresh - Automatic access token renewal using refresh tokens
- Protected Routes - Route protection with JWT verification
- TypeScript - Full TypeScript support with proper typing
- Comprehensive Tests - Full test suite using Vitest and Supertest
- Token Rotation - Refresh tokens are rotated on each use for security
- Node.js (v18 or higher)
- pnpm (recommended) or npm
-
Clone the repository
git clone https://github.com/nicholascostadev/fastify-jwt-auth-example cd fastify-jwt-auth-example -
Install dependencies
pnpm install
-
Start the development server
pnpm run start:dev
The server will start on http://localhost:8080
Authenticate a user with email and password.
Request Body:
{
"email": "john@doe.com",
"password": "123456"
}Response:
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Cookies Set:
refreshToken- HttpOnly, Secure, SameSite=Strict (30 days)
Refresh an expired access token using the refresh token stored in cookies.
Request: Requires refreshToken cookie
Response:
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Cookies Updated:
refreshToken- New refresh token (rotated for security)
Access a protected resource that requires valid JWT authentication.
Headers:
Authorization: Bearer <accessToken>
Response:
{
"message": "This is a protected route!",
"user": {
"userId": "user123",
"type": "access",
"id": "uuid",
"iat": 1234567890,
"exp": 1234567890
}
}curl -X POST http://localhost:8080/auth \
-H "Content-Type: application/json" \
-d '{"email": "john@doe.com", "password": "123456"}' \
-c cookies.txtcurl -X GET http://localhost:8080/private \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"curl -X POST http://localhost:8080/auth/refresh \
-b cookies.txt \
-c cookies.txt- Access Token Expiry: 15 minutes (short-lived for security)
- Refresh Token Expiry: 30 days (long-lived for user convenience)
- HttpOnly Cookies: Refresh tokens stored in HttpOnly cookies to prevent XSS
- Token Rotation: Refresh tokens are rotated on each use
- Type Validation: Separate token types for access and refresh
- Unique Token IDs: Each token has a unique ID to prevent replay attacks
pnpm run start:dev- Start development server with hot reloadpnpm run build- Build TypeScript to JavaScriptpnpm run start- Start production serverpnpm run test- Run tests oncepnpm run test:watch- Run tests in watch mode
The project includes comprehensive tests covering:
- Authentication with valid/invalid credentials
- Refresh token functionality
- Protected route access
- Token expiry and validation
- Security edge cases
Run tests with:
pnpm testThe project uses:
- Biome for code formatting and linting
- TypeScript for type safety
- Vitest for testing
Before deploying to production:
- Change JWT Secret: Replace
"your-jwt-secret-key"with a strong, random secret - Enable HTTPS: Set
secure: truefor cookies in production - Environment Variables: Use environment variables for sensitive configuration
- Database Integration: Replace hardcoded credentials with proper user management
- Rate Limiting: Add rate limiting to prevent brute force attacks
- Logging: Configure appropriate logging for monitoring
Nicholas Costa