Authentication Configuration
Auth is stateless JWT (HS256) over bcrypt-hashed credentials, with optional TOTP MFA and, on Enterprise, federated SSO. This page covers the token lifecycle, user provisioning, and the hardening surface an operator owns.
Flow
POST /api/auth/login resolves the user by email, verifies the submitted password against the stored bcrypt hash, and on success issues a JWT carrying userId, email, name, role, signed HS256 with JWT_SECRET. The client holds it in localStorage and sends Authorization: Bearer <token> on every call; the backend re-verifies signature and expiry per request. Because the token is stateless, anyone with JWT_SECRET can forge one for any user, keep it in a secret store, out of source and client code.
Password Hashing
bcrypt at cost 12, ~200-300ms/hash, per-hash salt, format $2b$12$.... Don't drop below 10.
Provisioning Users
Registration API
POST /api/auth/register
{ "email": "user@company.com", "password": "…", "name": "Jane Smith" }
Password is hashed server-side; default role is developer, an admin adjusts it afterward.
Direct insert (bootstrap admin / bulk)
node -e "require('bcrypt').hash('YourPassword123', 12).then(h => console.log(h));"
INSERT INTO users (email, password_hash, name, role, is_active)
VALUES ('admin@company.com', '$2b$12$…', 'Admin User', 'admin', true);
Roles
| Role | Access |
|---|---|
admin | Full access, incl. user management and system settings. |
developer | Author/edit scripts, request approvals. |
reviewer | Approve/reject scripts. |
deployer | Execute scripts, manage releases. |
viewer | Read-only. |
Token Lifecycle
TTL is JWT_EXPIRES_IN (default 8h). There is no refresh by design, on a 401 the client redirects to login. Logout clears client storage but, being stateless, the token stays cryptographically valid until expiry. To force a fleet-wide logout during an incident, rotate JWT_SECRET and restart, every outstanding signature fails at once.
Multi-Factor Authentication
Enterprise supports TOTP (RFC 6238) with 10 single-use backup codes stored as SHA-256 hashes; the TOTP secret is encrypted at rest with ENCRYPTION_KEY. Enrollment is POST /api/auth/mfa/setup then /enable.
Rate Limiting
Auth falls under the general bucket (GENERAL_RATE_LIMIT_MAX, default 2000 / 15 min per IP); DB DevOps routes use the wider DB_DEVOPS_RATE_LIMIT_MAX bucket. Breach returns 429. See Environment Variables for the knobs, and ensure the proxy forwards the real client IP or the per-IP keying is meaningless.
Hardening Checklist
- JWT_SECRET / ENCRYPTION_KEY, ≥32 chars (64 recommended), from a secret store. Rotating
ENCRYPTION_KEYorphans encrypted-at-rest data, back up first. - ENCRYPTION_KEY must be set, unset means secrets persist in plaintext behind a warning log.
- TLS in front, always. Terminate at the proxy; serve UI and API same-origin.
- Shortest practical
JWT_EXPIRES_IN. - Helmet sets security headers automatically; CSP permits
unsafe-evalsolely for the Monaco editor. - CORS allows only
CLIENT_URL, never*in production. - Least-privilege DB user.
- Bypass headers.
X-QA-Bypass-KeyandX-Agent-Keyskip auth/RBAC and are gated toNODE_ENV !== 'production', confirm prod actually setsNODE_ENV=production.
Enterprise SSO
Enterprise adds SAML, OIDC, and LDAP federation plus connectors for Azure AD, Okta, Google Workspace, and AWS Cognito, managed at /api/auth/idp/providers. See the Identity Providers page.