https://github.com/obeim/fastify-auth-template
A secure authentication & authorization starter template built with Fastify, TypeScript, and Prisma, following OWASP security best practices.
https://github.com/obeim/fastify-auth-template
authentication authorization e2e-testing fastify integration-test nodejs owasp-top-10 postgres prisma rest-api testing typescript unit-testing vitest
Last synced: about 2 months ago
JSON representation
A secure authentication & authorization starter template built with Fastify, TypeScript, and Prisma, following OWASP security best practices.
- Host: GitHub
- URL: https://github.com/obeim/fastify-auth-template
- Owner: obeim
- Created: 2025-09-11T21:05:44.000Z (9 months ago)
- Default Branch: main
- Last Pushed: 2025-12-11T20:20:29.000Z (6 months ago)
- Last Synced: 2025-12-13T00:49:58.509Z (6 months ago)
- Topics: authentication, authorization, e2e-testing, fastify, integration-test, nodejs, owasp-top-10, postgres, prisma, rest-api, testing, typescript, unit-testing, vitest
- Language: TypeScript
- Homepage:
- Size: 103 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Fastify Auth Template (TypeScript + Prisma)
A secure authentication & authorization starter template built with **Fastify**, **TypeScript**, and **Prisma**, following **OWASP security best practices**. Designed as a boilerplate for small to mid-sized projects.
## Table of Contents
- [Features](#features)
- [Tech Stack](#tech-stack)
- [Project Structure](#project-structure)
- [Getting Started](#getting-started)
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Environment Variables](#environment-variables)
- [Database Setup](#database-setup)
- [Development](#development)
- [API Endpoints](#api-endpoints)
- [Authentication Flow](#authentication-flow)
- [Role-Based Authorization](#role-based-authorization)
- [Testing](#testing)
- [Test Structure](#test-structure)
- [Running Tests](#running-tests)
- [Writing Tests](#writing-tests)
- [Security Best Practices](#security-best-practices)
- [Project Guidelines](#project-guidelines)
- [Contributing](#contributing)
---
## Features
- **JWT Authentication**
- Access token stored in memory (15-minute expiry)
- Refresh token stored in **HTTP-only secure cookies** (7-day expiry)
- Automatic token rotation on refresh
- **Role-based Authorization**
- Built-in roles: `USER`, `ADMIN`, `MODERATOR`
- Flexible route protection with role combinations
- **OWASP Security Standards**
- Secure cookie flags (`HttpOnly`, `Secure`, `SameSite=Strict`)
- Token rotation on refresh (prevents token replay attacks)
- CORS with strict origin & credentials
- Input validation with [TypeBox](https://github.com/sinclairzx81/typebox)
- Password hashing with bcrypt (10 rounds)
- Helmet for secure HTTP headers
- Rate limiting (100 requests/minute)
- **Developer Experience**
- Full TypeScript support with strict typing
- Hot reload in development
- ESLint + Husky for code quality
- Comprehensive test suite (Unit, Integration, E2E)
---
## Tech Stack
| Technology | Purpose |
| -------------------------------------------------- | ------------------------------------ |
| [Fastify](https://fastify.io/) | High-performance web framework |
| [TypeScript](https://www.typescriptlang.org/) | Type safety and developer experience |
| [Prisma](https://www.prisma.io/) | Database ORM with type-safe queries |
| [PostgreSQL](https://www.postgresql.org/) | Primary database |
| [Vitest](https://vitest.dev/) | Testing framework |
| [TypeBox](https://github.com/sinclairzx81/typebox) | Runtime type validation |
---
## Project Structure
```
fastify-auth-template/
├── src/
│ ├── server.ts # Application entry point
│ ├── config/
│ │ └── config.ts # Environment configuration
│ ├── controllers/
│ │ ├── auth.controller.ts # Authentication request handlers
│ │ └── users.controller.ts# User routes request handlers
│ ├── plugins/
│ │ ├── auth.ts # JWT authentication plugin
│ │ └── prisma.ts # Prisma database plugin
│ ├── routes/
│ │ ├── auth.ts # Authentication routes
│ │ └── users.ts # User routes (with role examples)
│ ├── services/
│ │ ├── auth.service.ts # Business logic for auth
│ │ └── auth.service.test.ts # Unit tests for auth service
│ ├── types/
│ │ ├── auth.d.ts # JWT payload type definitions
│ │ └── fastify.d.ts # Fastify request/reply types
│ └── validations/
│ └── auth.ts # TypeBox validation schemas
├── __tests__/
│ ├── helpers/
│ │ ├── test-app.ts # Test application builder
│ │ └── test-database.ts # Database utilities for tests
│ ├── e2e/
│ │ ├── auth.e2e.test.ts # End-to-end auth flow tests
│ │ └── users.e2e.test.ts # End-to-end user flow tests
│ └── integration/
│ ├── auth.integration.test.ts # Auth API integration tests
│ └── users.integration.test.ts # User routes integration tests
├── prisma/
│ ├── schema.prisma # Database schema
│ └── migrations/ # Database migrations
├── vitest.config.ts # Test configuration
├── tsconfig.json # TypeScript configuration
├── eslint.config.ts # ESLint configuration
└── package.json # Dependencies and scripts
```
### Directory Breakdown
| Directory | Purpose |
| ------------------------ | --------------------------------------------------------- |
| `src/config/` | Application configuration and environment variables |
| `src/controllers/` | HTTP request handlers (thin layer, delegates to services) |
| `src/plugins/` | Fastify plugins for cross-cutting concerns |
| `src/routes/` | Route definitions and middleware attachment |
| `src/services/` | Business logic layer (testable, framework-agnostic) |
| `src/types/` | TypeScript type definitions and declarations |
| `src/validations/` | Request/response validation schemas |
| `__tests__/helpers/` | Shared test utilities and fixtures |
| `__tests__/e2e/` | End-to-end tests (complete user flows) |
| `__tests__/integration/` | Integration tests (API endpoint testing) |
| `prisma/` | Database schema and migrations |
---
## Getting Started
### Prerequisites
- **Node.js** >= 18.x
- **pnpm** (recommended) or npm
- **PostgreSQL** >= 13.x
- **Docker** (optional, for containerized development)
### Installation
```bash
# Clone the repository
git clone
cd fastify-auth-template
# Install dependencies
pnpm install
# Generate Prisma client
pnpm prisma generate
```
### Environment Variables
Create a `.env` file in the root directory:
```env
# Server
PORT=3000
HOST=0.0.0.0
NODE_ENV=development
# Database
DATABASE_URL="postgresql://user:password@localhost:5432/mydb?schema=public"
# JWT
JWT_SECRET="your-super-secret-jwt-key-change-in-production"
# CORS (JSON array of allowed origins)
CORS_ALLOWED_ORIGINS='["http://localhost:3000", "http://localhost:5173"]'
```
> ⚠️ **Important**: Never commit `.env` to version control. Use strong, unique secrets in production.
### Database Setup
```bash
# Run migrations
pnpm prisma migrate dev
# (Optional) Seed the database
pnpm prisma db seed
# View database in Prisma Studio
pnpm prisma studio
```
---
## Development
```bash
# Start development server with hot reload
pnpm dev
# Build for production
pnpm build
# Start production server
pnpm start
# Type checking
pnpm check-types
# Linting
pnpm lint
```
---
## API Endpoints
### Authentication (`/api/v1/auth`)
| Method | Endpoint | Description | Auth Required |
| ------ | ----------- | ---------------------------- | ------------- |
| `POST` | `/register` | Register a new user | No |
| `POST` | `/login` | Login and get tokens | No |
| `GET` | `/refresh` | Refresh access token | Cookie |
| `GET` | `/logout` | Logout and invalidate tokens | Yes |
### User Routes (`/api/v1`)
| Method | Endpoint | Description | Required Role |
| ------ | ------------------------- | ------------------------ | ---------------------- |
| `GET` | `/publicRoute` | Public endpoint | None |
| `GET` | `/authRoute` | Authenticated users only | Any authenticated |
| `GET` | `/adminRoute` | Admin only | `ADMIN` |
| `GET` | `/moderatorRoute` | Moderator only | `MODERATOR` |
| `GET` | `/moderatorAndAdminRoute` | Admin or Moderator | `ADMIN` or `MODERATOR` |
### Request/Response Examples
**Register**
```bash
POST /api/v1/auth/register
Content-Type: application/json
{
"email": "user@example.com",
"password": "securePassword123",
"name": "John Doe"
}
```
**Login**
```bash
POST /api/v1/auth/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "securePassword123"
}
# Response
{
"user": { "id": 1, "name": "John Doe", "role": "USER" },
"accessToken": "eyJhbGciOiJIUzI1NiIs..."
}
# + HttpOnly cookie: refreshToken
```
**Access Protected Route**
```bash
GET /api/v1/authRoute
Authorization: Bearer
```
---
## Authentication Flow
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Client │ │ Server │ │ Database │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
│ 1. POST /login │ │
│──────────────────>│ │
│ │ 2. Verify creds │
│ │──────────────────>│
│ │<──────────────────│
│ │ │
│ 3. Access Token │ │
│ + Refresh Cookie│ │
│<──────────────────│ │
│ │ │
│ 4. GET /protected │ │
│ + Bearer Token │ │
│──────────────────>│ │
│ │ 5. Verify JWT │
│ │ │
│ 6. Response │ │
│<──────────────────│ │
│ │ │
│ 7. GET /refresh │ │
│ + Cookie │ │
│──────────────────>│ │
│ │ 8. Validate & │
│ │ Rotate Token │
│ │──────────────────>│
│ │<──────────────────│
│ 9. New Tokens │ │
│<──────────────────│ │
│ │ │
```
### Token Specifications
| Token | Storage | Expiry | Purpose |
| ------------- | ---------------- | ---------- | ------------------ |
| Access Token | Client memory | 15 minutes | API authentication |
| Refresh Token | HTTP-only cookie | 7 days | Token renewal |
---
## Role-Based Authorization
### Available Roles
```typescript
enum UserRole {
USER // Default role for new registrations
ADMIN // Full administrative access
MODERATOR // Limited administrative access
}
```
### Protecting Routes
```typescript
// Single role
fastify.get(
"/admin",
{
preHandler: [fastify.authenticate, fastify.authorize([UserRole.ADMIN])],
},
handler
);
// Multiple roles (OR logic)
fastify.get(
"/staff",
{
preHandler: [
fastify.authenticate,
fastify.authorize([UserRole.ADMIN, UserRole.MODERATOR]),
],
},
handler
);
// Any authenticated user
fastify.get(
"/profile",
{
preHandler: [fastify.authenticate],
},
handler
);
```
---
## Testing
### Test Structure
```
__tests__/
├── helpers/ # Shared test utilities
│ ├── test-app.ts # Builds test Fastify instance
│ └── test-database.ts # Database seeding & cleanup
├── e2e/ # End-to-End Tests
│ ├── auth.e2e.test.ts # Complete auth user journeys
│ └── users.e2e.test.ts # Multi-user scenarios
└── integration/ # Integration Tests
├── auth.integration.test.ts # Auth API endpoints
└── users.integration.test.ts # User route permissions
src/services/
└── auth.service.test.ts # Unit tests (mocked dependencies)
```
### Test Types
| Type | Location | Purpose | Database |
| --------------- | ------------------------ | -------------------------------- | -------------- |
| **Unit** | `src/**/*.test.ts` | Test business logic in isolation | Mocked |
| **Integration** | `__tests__/integration/` | Test API endpoints | Real (test DB) |
| **E2E** | `__tests__/e2e/` | Test complete user flows | Real (test DB) |
### Running Tests
```bash
# Run all tests
pnpm test
# Run tests in watch mode
pnpm test -- --watch
# Run specific test file
pnpm test -- auth.integration.test.ts
# Run with coverage
pnpm test -- --coverage
# Run only unit tests
pnpm test -- src/
# Run only integration tests
pnpm test -- __tests__/integration/
# Run only E2E tests
pnpm test -- __tests__/e2e/
```
### Writing Tests
**Unit Test Example** (with mocks)
```typescript
import { describe, it, expect, vi, beforeEach } from "vitest";
import AuthService from "./auth.service";
const prismaMock = {
user: {
findUnique: vi.fn(),
create: vi.fn(),
},
};
describe("AuthService", () => {
let authService: AuthService;
beforeEach(() => {
vi.resetAllMocks();
authService = new AuthService(fastifyMock as any);
});
it("should throw error for invalid credentials", async () => {
prismaMock.user.findUnique.mockReturnValue(null);
await expect(
authService.loginUser("test@email.com", "pass")
).rejects.toThrow("Invalid credentials");
});
});
```
**Integration Test Example** (real API calls)
```typescript
import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { buildTestApp, generateTestEmail } from "../helpers/test-app";
describe("Auth API", () => {
let app: FastifyInstance;
beforeAll(async () => {
app = await buildTestApp();
});
afterAll(async () => {
await app.close();
});
it("should register a user", async () => {
const response = await app.inject({
method: "POST",
url: "/api/v1/auth/register",
payload: {
email: generateTestEmail(),
password: "password123",
name: "Test User",
},
});
expect(response.statusCode).toBe(201);
});
});
```
**E2E Test Example** (complete flows)
```typescript
describe("User Journey", () => {
it("register → login → access protected → logout", async () => {
// 1. Register
const register = await app.inject({
/* ... */
});
expect(register.statusCode).toBe(201);
// 2. Login
const login = await app.inject({
/* ... */
});
const { accessToken } = JSON.parse(login.payload);
// 3. Access protected route
const protected = await app.inject({
method: "GET",
url: "/api/v1/authRoute",
headers: { authorization: `Bearer ${accessToken}` },
});
expect(protected.statusCode).toBe(200);
// 4. Logout
const logout = await app.inject({
/* ... */
});
expect(logout.statusCode).toBe(200);
});
});
```
---
## Security Best Practices
This template implements several OWASP recommendations:
| Practice | Implementation |
| --------------------------- | ---------------------------------------------------- |
| **Secure Password Storage** | bcrypt with 10 salt rounds |
| **JWT Security** | Short-lived access tokens, HTTP-only refresh cookies |
| **Token Rotation** | Refresh tokens are rotated on each use |
| **CORS** | Strict origin allowlist with credentials |
| **HTTP Headers** | Helmet with CSP, CORP, and other security headers |
| **Rate Limiting** | 100 requests per minute per IP |
| **Input Validation** | TypeBox schema validation on all inputs |
| **Cookie Security** | `HttpOnly`, `Secure`, `SameSite=Strict` |
### Production Checklist
- [ ] Use strong, unique `JWT_SECRET` (32+ characters)
- [ ] Set `NODE_ENV=production`
- [ ] Configure proper `CORS_ALLOWED_ORIGINS`
- [ ] Use HTTPS (required for `Secure` cookies)
- [ ] Set up database connection pooling
- [ ] Enable logging and monitoring
- [ ] Review and adjust rate limits
- [ ] Set up health checks
---
## Project Guidelines
### Code Organization Rules
1. **Controllers** - Thin layer, only HTTP concerns (request/response)
2. **Services** - Business logic, testable without HTTP context
3. **Plugins** - Fastify decorators and hooks
4. **Routes** - Route definitions with middleware attachment
5. **Validations** - TypeBox schemas, no logic
### Adding New Features
1. **New Route**
```
1. Create validation schema in src/validations/
2. Create/update service in src/services/
3. Create/update controller in src/controllers/
4. Register route in src/routes/
5. Add tests (unit + integration)
```
2. **New Role**
```
1. Add to UserRole enum in prisma/schema.prisma
2. Run pnpm prisma migrate dev
3. Update route preHandlers as needed
4. Add E2E tests for role access
```
### File Naming Conventions
| Type | Pattern | Example |
| ----------------- | ------------------------ | -------------------------- |
| Controllers | `*.controller.ts` | `auth.controller.ts` |
| Services | `*.service.ts` | `auth.service.ts` |
| Routes | `*.ts` (in routes/) | `auth.ts` |
| Validations | `*.ts` (in validations/) | `auth.ts` |
| Unit Tests | `*.test.ts` (co-located) | `auth.service.test.ts` |
| Integration Tests | `*.integration.test.ts` | `auth.integration.test.ts` |
| E2E Tests | `*.e2e.test.ts` | `auth.e2e.test.ts` |