A production-ready authentication system combining Fastify REST API and uWebSockets.js for real-time communication, featuring JWT-based authentication with automatic token rotation and refresh token families.
- Fastify API: REST endpoints for authentication and business logic
- uWebSockets.js: High-performance WebSocket server for real-time communication
- PostgreSQL: User data and refresh token storage
- Nginx: Reverse proxy with rate limiting
- Docker Compose: Complete containerized deployment
- Secure login/logout with bcrypt password hashing
- JWT access tokens (RS256) with 15-minute expiry
- HTTP-only refresh tokens with 7-day expiry
- Automatic token rotation on refresh
- Token family tracking for replay attack prevention
- RS256 asymmetric encryption (2048-bit keys)
- Refresh token families prevent token reuse attacks
- Token revocation on logout and security events
- Rate limiting on authentication endpoints (configurable)
- Secure cookie handling (HttpOnly, Secure, SameSite)
- WebSocket authentication using JWT access tokens
- Automatic token expiry detection (2-minute warning)
- Seamless re-authentication without disconnection
- WebSocket-specific state management (presence, typing indicators)
- Access tokens: Short-lived (15 min), bearer tokens for API requests
- Refresh tokens: Long-lived (7 days), HTTP-only cookies for token renewal
- Automatic refresh before expiry
- Token family-based revocation cascade
- Session expiry with automatic redirect
fastify-uws-auth/
βββ docker-compose.yml # Orchestrates all services
βββ docker-compose.dev.yml # Dev mode override
βββ generate-keys.sh # Generate RSA keypair
βββ start.sh # Quick start script
βββ README.md # Main documentation
βββ SECURITY.md # Security documentation
βββ client-demo.html # Interactive demo
βββ .gitignore # Git ignore rules
β
βββ keys/ # RSA keys (gitignored)
β βββ private.pem # Private key for signing
β βββ public.pem # Public key for verification
β
βββ nginx/
β βββ nginx.conf # Reverse proxy config
β
βββ postgres-init/
β βββ 01-init.sql # Database schema
β
βββ fastify-api/ # REST API Server
β βββ Dockerfile
β βββ package.json
β βββ tsconfig.json
β βββ src/
β βββ index.ts # Server entry point
β βββ routes.ts # Auth endpoints
β βββ database.ts # Database layer
β βββ jwt.ts # JWT utilities
β
βββ uws-server/ # WebSocket Server
βββ Dockerfile
βββ package.json
βββ tsconfig.json
βββ src/
βββ index.ts # WebSocket server
βββ jwt.ts # JWT verification
- Docker and Docker Compose
- Node.js 20+ (for local development)
- OpenSSL (for key generation)
- Clone the repository
- Run the setup script:
./start.sh
This will:
- Generate RSA keys if not present
- Create package-lock.json files
- Build Docker images
- Start all services
- Initialize database with test user
chmod +x generate-keys.sh
./generate-keys.shThis creates:
keys/private.pem- Private key for signing JWTs (keep secure!)keys/public.pem- Public key for verification
- Web Interface: http://localhost
- Fastify API: http://localhost/api
- WebSocket: ws://localhost/ws
- Health Check: http://localhost/health
- Username:
testuser - Password:
password123
POST /api/login
- Request:
{ "username": "string", "password": "string" } - Response:
{ "accessToken": "string", "user": { "id": number, "username": "string" } } - Sets HTTP-only refresh token cookie
POST /api/logout
- Requires: Refresh token cookie
- Revokes refresh token and clears cookie
POST /api/refresh
- Requires: Refresh token cookie
- Response:
{ "accessToken": "string" } - Rotates refresh token automatically
GET /api/protected
- Requires:
Authorization: Bearer <access_token> - Response:
{ "message": "string", "user": { ... } }
GET /api/health
- Public health check endpoint
- Response:
{ "status": "ok", "timestamp": "ISO-8601" }
ws://localhost/ws?token=<access_token>
Client to Server:
{ "type": "ping" }
{ "type": "reauth", "access": "<new_access_token>" }Server to Client:
{ "type": "connected", "payload": { "userId": number, "username": "string" } }
{ "type": "pong", "payload": { "timestamp": number } }
{ "type": "token_expiring", "payload": { "expiresAt": number } }
{ "type": "error", "payload": { "message": "string", "requireReauth": boolean } }- User submits credentials
- Server validates and creates token family
- Server issues access token (JWT) and refresh token
- Refresh token stored in database and sent as HTTP-only cookie
- Access token returned in response body
- Client sends refresh token cookie to /api/refresh
- Server verifies token and checks revocation status
- Server marks old token as used
- Server generates new access token and refresh token (same family)
- New tokens returned/set
- Access token expires after 15 minutes
- WebSocket detects expiry 2 minutes before
- Client automatically refreshes tokens
- Client re-authenticates WebSocket with new access token
- Connection remains active throughout
- Revoked token is reused
- Server detects token reuse
- Server revokes entire token family
- User forced to re-authenticate
Fastify API:
PORT: API port (default: 3000)DB_HOST: PostgreSQL host (default: postgres)DB_PORT: PostgreSQL port (default: 5432)DB_USER: Database user (default: authuser)DB_PASSWORD: Database password (default: authpass)DB_NAME: Database name (default: authdb)PRIVATE_KEY_PATH: Path to RSA private keyPUBLIC_KEY_PATH: Path to RSA public keyACCESS_TOKEN_EXPIRY: Access token lifetime (default: 15m)REFRESH_TOKEN_EXPIRY: Refresh token lifetime (default: 7d)
uWebSockets.js:
PORT: WebSocket port (default: 3001)PUBLIC_KEY_PATH: Path to RSA public key
Nginx:
- Rate limit: 30 requests/minute (configurable in nginx.conf)
- Burst: 10 requests
id: Serial primary keyusername: Unique usernamepassword_hash: Bcrypt hashed passwordcreated_at: Account creation timestamp
id: Serial primary keyuser_id: Foreign key to userstoken_hash: SHA-256 hash of tokentoken_family: UUID for token family trackingexpires_at: Token expiration timestampis_revoked: Revocation flagrevoked_at: Revocation timestampcreated_at: Token creation timestamplast_used_at: Last usage timestamp
# Install dependencies
cd fastify-api && npm install
cd ../uws-server && npm install
# Run services
docker compose up -d
# View logs
docker compose logs -f
# Stop services
docker compose down- Key Management: RSA keys are gitignored and must be generated per environment
- Password Storage: Bcrypt with salt rounds of 10 (configurable)
- Token Storage: Refresh tokens stored as SHA-256 hashes in database
- Cookie Security: HTTP-only, Secure (HTTPS only), SameSite=Strict
- Rate Limiting: Prevents brute force attacks on authentication endpoints
- Token Families: Prevents token replay attacks through family-based revocation
- Database Cleanup: Automatic cleanup of expired tokens
- Use environment-specific RSA keys (do not share between environments)
- Enable HTTPS/TLS termination at Nginx
- Configure appropriate rate limits based on traffic patterns
- Set up database backups and replication
- Monitor token refresh patterns for anomalies
- Implement log aggregation and alerting
- Use secrets management for sensitive configuration
- Consider shorter token lifetimes for high-security applications
- Language: TypeScript
- Runtime: Node.js 22
- REST Framework: Fastify 5.x
- WebSocket: uWebSockets.js (native C++ bindings)
- Database: PostgreSQL 16
- Reverse Proxy: Nginx
- Authentication: JWT (jsonwebtoken)
- Password Hashing: bcrypt
- Containerization: Docker & Docker Compose
This project is provided as-is for educational and production use. MIT License - See LICENSE file for details
For issues, questions, or contributions, please refer to the project documentation or create an issue in the repository.
