Skip to main content

Authentication & Headers

Minimal does not have its own authentication system. It operates on a trusted subsystem model — your existing infrastructure authenticates the user and injects identity headers before the request reaches Minimal.

Minimal reads those headers and acts on them. It never sees the original token, session, or credential.


The Model

Client → [Your Gateway / App Server] → Minimal

└── validates token
resolves user identity
injects headers
forwards to Minimal

Your gateway or app server is the only trust boundary. Once a request passes through it, Minimal assumes it is legitimate.

Never call Minimal directly from the client

All requests must go through your gateway or app server. A client that calls Minimal directly — bypassing your gateway — can forge any identity by setting arbitrary headers.


Header Reference

There are two sets of headers — System API headers used only during setup, and Auto API headers used on every data request.

Auto API Headers

Required on every GET, POST, PUT, and DELETE request:

HeaderTypeRequiredDescription
X-Org-IdString✅ YesOrganization the request belongs to
X-Project-IdString✅ YesProject (database config) to use
X-Space-IdString✅ YesWorkspace within the project
X-User-IdString✅ YesThe authenticated user making the request
X-User-RolesString✅ YesComma-separated roles — e.g. admin, users
Content-TypeStringPOST onlyMust be application/json

Missing any of the first five returns 400 Bad Request.

System API Headers

Required only for org and project creation — never sent from a client:

HeaderTypeRequiredDescription
X-User-IdString✅ YesThe user performing the setup
X-Server-KeyString✅ YesServer trust credential — from config.yml
Content-TypeString✅ YesMust be application/json
X-Server-Key is not a gateway header

X-Server-Key must be injected by your backend service only — not your gateway, not your client. It is a service-to-service credential. Treat it the same as a database password.


Who Sets Each Header

HeaderSet byWhen
X-User-IdGatewayEvery request — resolved from token
X-User-RolesGatewayEvery request — resolved from token claims or roles DB
X-Org-IdGateway or App serverEvery request — may be in token or resolved per request
X-Project-IdGateway or App serverEvery request — may vary per request
X-Space-IdGateway or App serverEvery request — may vary per request
X-Server-KeyBackend service onlySystem API only — org and project creation

Integration Patterns

Pattern 1 — Gateway sets everything

Your API gateway validates the token and injects all five Auto API headers. The client only sends a Bearer token — it never sets Minimal headers directly.

Client (Bearer token)
→ Gateway (validates, resolves all 5 headers, injects)
→ Minimal

Best for centralised auth where org/project/space are fixed in the token claims.

// Kong / custom gateway plugin example
const claims = await verifyJWT(token);

request.headers['X-User-Id'] = claims.sub;
request.headers['X-User-Roles'] = claims.roles.join(', ');
request.headers['X-Org-Id'] = claims.org_id;
request.headers['X-Project-Id'] = claims.project_id;
request.headers['X-Space-Id'] = claims.space_id;

Pattern 2 — App server sets headers

Your gateway validates the token and blocks unauthenticated requests. Your backend app resolves org/project/space and injects the headers before calling Minimal.

Client (Bearer token)
→ Gateway (validates token, passes through)
→ App Server (resolves context, injects headers)
→ Minimal

Best when org/project/space are dynamic — chosen per request by the app, not fixed in the token.

// Node/Express backend example
app.use('/minimal', async (req, res, next) => {
const session = req.user; // already validated by gateway

req.headers['X-User-Id'] = session.userId;
req.headers['X-User-Roles'] = session.roles.join(', ');
req.headers['X-Org-Id'] = session.orgId;
req.headers['X-Project-Id'] = req.query.projectId; // dynamic per request
req.headers['X-Space-Id'] = req.query.spaceId;

next();
});

Pattern 3 — nginx auth_request

nginx forwards the token to an internal auth service via auth_request. The auth service validates it and returns the identity headers. nginx picks them up and injects them into the Minimal request.

Client (Bearer token)
→ nginx
→ auth_request → your auth service (returns headers)
→ nginx injects headers
→ Minimal

Best when you want zero application-layer code for header injection.

location /minimal/api/ {
auth_request /auth/validate;
auth_request_set $user_id $upstream_http_x_user_id;
auth_request_set $user_roles $upstream_http_x_user_roles;
auth_request_set $org_id $upstream_http_x_org_id;
auth_request_set $project_id $upstream_http_x_project_id;
auth_request_set $space_id $upstream_http_x_space_id;

proxy_set_header X-User-Id $user_id;
proxy_set_header X-User-Roles $user_roles;
proxy_set_header X-Org-Id $org_id;
proxy_set_header X-Project-Id $project_id;
proxy_set_header X-Space-Id $space_id;

proxy_pass http://127.0.0.1:3045/;
}

location = /auth/validate {
internal;
proxy_pass http://127.0.0.1:4000/validate;
proxy_pass_request_body off;
proxy_set_header Content-Length '';
proxy_set_header X-Original-URI $request_uri;
}

X-Server-Key — Backend service only

X-Server-Key is used only for System API calls (org and project creation). It must always come from a backend service — never from a gateway or client:

Client
→ Gateway ← does NOT handle X-Server-Key
→ Backend App ← reads from process.env, injects X-Server-Key
→ Minimal System API
// Backend service — org creation example
const response = await fetch('https://your-domain.com/minimal/system/api/v1/org', {
method: 'POST',
headers: {
'X-User-Id': req.user.id,
'X-Server-Key': process.env.MINIMAL_SERVER_KEY, // never hardcoded
'Content-Type': 'application/json',
},
body: JSON.stringify(req.body),
});

ID Format

All IDs in Minimal use ULID format — Universally Unique Lexicographically Sortable Identifiers.

01KBY3K9NDC5XW523M2V1Z0373

ULIDs are:

  • 26 characters, uppercase alphanumeric
  • Time-sortable — the first 10 characters encode the timestamp
  • URL-safe — no special characters
  • Globally unique — safe to generate client-side or server-side
Generating ULIDs
LanguageLibrary
JavaScript / Nodeulidnpm install ulid
Pythonpython-ulidpip install python-ulid
Gogithub.com/oklog/ulid
Javacom.github.f4b6a3:ulid-creator
Onlineulidgenerator.com

Common Errors

CodeCauseFix
400 Bad RequestOne or more Auto API headers missingEnsure all five headers are injected on every request
401 UnauthorizedX-User-Id is missingAlways pass X-User-Id
403 ForbiddenX-Server-Key wrong or missing on System APICheck server_key value in config.yml
403 ForbiddenRole not permitted for this operationCheck X-User-Roles value and auto_api.roles in config.yml