Security Configuration
JWT Authentication
SLOzy uses HMAC-SHA256 signed JWT tokens for API authentication.
| Variable | Default | Recommendation |
|---|---|---|
JWT_SECRET | dev_change_me_in_production_min_32_chars | Generate with openssl rand -base64 48 |
JWT_EXPIRATION | 8h | 8h for web apps, 1h for high-security environments |
REFRESH_TOKEN_EXPIRATION | 30d | Rotate refresh tokens periodically |
Important: The default JWT_SECRET is hardcoded for development only. In production, always set it to a cryptographically random value. The secret must be at least 32 characters.
Tokens are sent via the Authorization: Bearer <token> header. The backend validates every protected route using a middleware in internal/middleware/.
Two-Factor Authentication (2FA)
2FA support is available in the UI but requires SMTP configuration in .env to send verification codes:
| Variable | Default | Description |
|---|---|---|
SMTP_HOST | — | SMTP server hostname |
SMTP_PORT | 587 | SMTP server port |
SMTP_USER | — | SMTP username |
SMTP_PASSWORD | — | SMTP password |
SMTP_FROM | — | Sender email address |
Without these variables, 2FA will show "Enable 2FA" in the UI but cannot deliver codes. Planned support for authenticator apps (TOTP) is not yet implemented.
CORS
Cross-Origin Resource Sharing is controlled by:
| Variable | Default | Description |
|---|---|---|
CORS_ENABLED | true | Enable/disable CORS headers |
CORS_ORIGINS | http://localhost:3000,http://localhost:8080 | Comma-separated allowed origins |
In production, restrict CORS_ORIGINS to your actual frontend domain:
CORS_ORIGINS=https://slozy.yourdomain.comRate Limiting
Rate limiting is enforced at two levels:
Application Level (Go backend)
| Variable | Default | Description |
|---|---|---|
RATE_LIMIT_REQUESTS_PER_SECOND | 10 | General API per-second limit |
RATE_LIMIT_SLO_CREATES_PER_SECOND | 5 | Stricter limit for the SLO creation endpoint |
Nginx Level (recommended for production)
The provided deploy/nginx.conf sets up zone-based rate limiting:
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=slo_create:10m rate=5r/s;Two zones are defined:
api— 10 requests/second with burst 20, applied to all/api/routesslo_create— 5 requests/second with burst 10, applied to/api/slo/create
Security Headers
The production Nginx config (deploy/production/nginx.prod.conf) sets these headers:
| Header | Value |
|---|---|
X-Frame-Options | DENY |
X-Content-Type-Options | nosniff |
X-XSS-Protection | 1; mode=block |
Referrer-Policy | strict-origin-when-cross-origin |
Content-Security-Policy | default-src 'self' |
Strict-Transport-Security | max-age=31536000; includeSubDomains; preload |
The dev Nginx config (deploy/nginx.conf) uses a slightly more permissive CSP to allow API calls during development.
Additional Security Settings (.env.production)
| Variable | Default | Description |
|---|---|---|
ENABLE_SECURITY | true | Master toggle for all security middleware |
ENABLE_RATE_LIMIT | true | Enables Go-side rate limiting |
GLOBAL_RATE_LIMIT | 1000 | Global requests/second across all clients |
PER_IP_RATE_LIMIT | 100 | Per-IP requests/second |
ENABLE_IP_WHITELIST | false | Restrict access to specific IPs/CIDRs |
IP_WHITELIST | 127.0.0.1,10.0.0.0/8 | Allowed IP ranges |
Secrets Management
For production, never store secrets in environment variables in plain text. Recommended approaches:
- Kubernetes Secrets — store
JWT_SECRET,POSTGRES_PASSWORD,REDIS_PASSWORDas k8s secrets - External Secrets Operator — sync from AWS Secrets Manager / GCP Secret Manager
- HashiCorp Vault — inject secrets via Vault Agent sidecar
kubectl create secret generic slozy-secrets \
--from-literal=jwt-secret="$(openssl rand -base64 48)" \
--from-literal=postgres-password="<strong-password>" \
-n slozy