API and browser
JSON API under /v1 with access and refresh tokens, plus server-rendered pages with session sign-in. OpenAPI docs at /docs.
REST API, sign-in with a second factor, password reset by email, account self-service and user management in one project. The groundwork is secured and tested, so your code starts with the actual job.
content-security-policy
default-src 'self';base-uri 'none';script-src 'self';script-src-attr 'none';style-src 'self';img-src 'self';font-src 'self';connect-src 'self';manifest-src 'self';media-src 'none';worker-src 'none';form-action 'self';frame-ancestors 'none';object-src 'none'
strict-transport-security
max-age=63072000; includeSubDomains; preload
cross-origin-opener-policy
same-origin
cross-origin-embedder-policy
require-corp
cross-origin-resource-policy
same-origin
x-frame-options
DENY
x-content-type-options
nosniff
referrer-policy
no-referrer
permissions-policy
accelerometer=(), camera=(), display-capture=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=(), browsing-topics=()
cache-control
no-store
Before your code sees a request, it passes seven stations. Scroll along.
Every request gets an ID that shows up in each log line and on error pages. Anything larger than 100 KB is rejected before the server reads it.
X-Request-Id: 7c1e…
A Content Security Policy without inline scripts, HSTS, COOP, COEP and a Permissions-Policy that blocks camera, microphone and location.
Content-Security-Policy: default-src 'self'
300 requests per 15 minutes and IP, 10 failed attempts on sign-in and password forms. With RATE_LIMIT_STORE=database several instances share the counters.
RateLimit-Policy: 300;w=900
Forms carry a CSRF token that is masked anew each time it is rendered. Posts from other sites already fail the Fetch Metadata check.
Sec-Fetch-Site: same-origin
Joi checks every field against a schema and rejects unknown ones. MongoDB operators and prototype keys are stripped first.
400 "email" must be a valid email
Session cookie or JWT, the role comes from the database, with a second factor if the user turned it on. After a password change, old tokens and sessions stop working.
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9…
Errors reveal no internals, only an error ID. Duration and status end up as a Prometheus metric.
200 OK
JSON API under /v1 with access and refresh tokens, plus server-rendered pages with session sign-in. OpenAPI docs at /docs.
Sign-up, email confirmation, forgotten password, two-factor with an authenticator app and recovery codes.
Profile, password, signed-in devices, activity log, data export and deletion, as the GDPR requires.
Find users, assign roles and delete accounts, in the browser and through the API with the same rights.
Interface and emails in German and English. Another language is one more file.
Docker, health checks, Prometheus metrics, structured logs with request IDs and a load test for every endpoint.
Node.js 22 is enough: the data goes to SQLite until DATABASE_URL names MongoDB, PostgreSQL, MySQL or another server. Setup writes a .env with random keys.
npm run setupCreate .env with your own keysnpm run create-adminCreate the first administratornpm run devStart the server, restart on changes