JWT Authentication Overview
This document explains how authentication works in the VMS backend API.
The backend provides authentication independently from the frontend.
The Next.js frontend acts as a pure HTTP client that consumes API endpoints.
Stack
- Backend: Express + PostgreSQL + Prisma
- Frontend: Next.js (pure HTTP client)
- Auth model: JWT access token + refresh token cookie
1. Authentication Architecture
The system uses a two-token strategy:
| Token | Purpose | Storage | Lifetime |
|---|---|---|---|
| Access Token (JWT) | Authenticate API requests | Client memory | Short (e.g. 15 minutes) |
| Refresh Token | Obtain new access token | HttpOnly cookie | Longer (e.g. 7 days) |
2. Access Token (JWT)
The access token is a JSON Web Token (JWT) signed using the HS256 algorithm.
Example request header:
Authorization: Bearer <jwt_token>The backend verifies the token using a server secret stored in:
JWT_SECRET3. Typical JWT Payload
Example payload:
{
"sub": "user_id",
"email": "admin@example.com",
"role": "ADMIN",
"iat": 1710000000,
"exp": 1710000900
}Field description:
| Field | Meaning |
|---|---|
| sub | User ID |
| User email | |
| role | Authorization role |
| iat | Issued at timestamp |
| exp | Expiration timestamp |
4. Authentication Flow
Step 1 — Login
POST /api/auth/loginResponse:
{
"accessToken": "<jwt>"
}Cookie set:
refresh_token (HttpOnly)Step 2 — Call Protected API
Client sends:
Authorization: Bearer <jwt_token>Example:
GET /api/ordersStep 3 — Token Expired
If the access token expires:
POST /api/auth/refreshThe refresh token cookie is automatically sent.
Response returns a new access token.
5. Refresh Token
Refresh tokens are:
- Stored as HttpOnly cookies
- Not accessible from JavaScript
- Sent automatically by the browser
Default cookie settings:
Name: refresh_token
HttpOnly: true
Path: /api/auth
Secure: true (production)
SameSite: Strict6. JWT Secret
The JWT secret is a strong random string used to sign tokens.
Environment variable:
JWT_SECRET=<secure_random_string>Never commit this value to Git.
7. How to Generate a Secure JWT Secret Using Node.js
Run the following command in your terminal:
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"Example output:
3e8c5b2c71f9a8f2f50d38b9f6c44a9b7f62f80a9fbbd4a2c86f4c4e33f5c74d4a4b6a7f0a7d8a6b5e2e2d6f8e5d4a1bAdd it to your .env file:
JWT_SECRET=3e8c5b2c71f9a8f2f50d38b9f6c44a9b7f62f80a9fbbd4a2c86f4c4e33f5c74d4a4b6a7f0a7d8a6b5e2e2d6f8e5d4a1b8. Recommended Token Expiration
Typical values:
ACCESS_TOKEN_TTL = 15 minutes
REFRESH_TOKEN_TTL = 7 daysExample .env configuration:
JWT_ACCESS_TTL=15m
JWT_REFRESH_TTL=7d9. Security Best Practices
Always follow these rules:
Keep JWT_SECRET private
- Store only in environment variables
- Never commit to Git
Use short-lived access tokens
Reduces damage if leaked.
Use HttpOnly refresh cookies
Prevents XSS attacks.
Use HTTPS in production
Required for secure cookies.
Validate roles server-side
Never trust role data from frontend.
10. Backend Responsibility
Authentication logic lives entirely in the Express API.
The Next.js frontend:
- does not manage sessions
- does not store refresh tokens
- only sends HTTP requests
The backend remains the single source of truth for authentication.