Documentation/Managing Your App/Security
Security
How Supero authenticates requests, isolates your tenants' data, and gives you the building blocks — access control, client-side field encryption, and audit logging (being rebuilt) — to protect what you build.
Overview
This page is a security orientation for developers building on Supero: how requests are authenticated, how API keys are structured and scoped, how tenant data is isolated, what gets audit-logged, and where to go for the deeper mechanics of access control and field encryption. It doesn't replace those dedicated pages — it's the map that ties them together.
- •Authentication — every request carries either a signed-in user's JWT or a service API key; both resolve to the same role/permission model server-side.
- •Authorization — a role catalog (RBAC) plus, for your own schemas, object-level access policies that narrow entities, records, and fields. Covered in depth on the Access Control (RBAC) page.
- •Isolation — your data is scoped to your domain, and further scoped through the project/tenant hierarchy; there is no cross-domain query capability.
- •Field protection — an opt-in, client-held encryption mechanism for individual field values, covered in depth on the Field Encryption page.
- •Audit logging — being rebuilt with per-user attribution; staff access to your domain is audited today. See Audit Logging below for the current status.
Authentication
Supero accepts two kinds of credentials, and they are not interchangeable: a JWT for a signed-in user session, or an API key for server-to-server access. Send exactly one, in the header it belongs in.
| Credential | Header | Lifetime | Typical use |
|---|---|---|---|
| JWT access token | Authorization: Bearer <token> | 8 hours; refresh token lasts 7 days | A signed-in user inside your app |
| API key | X-API-Key: ak_... | Until you delete it — see the note on revoking below | Backend services, scripts, server-to-server integration |
⚠️ Don't cross the streams
Never send an API key as a Bearer token, and never send a JWT as X-API-Key. Each credential is validated on its own header and treated as an opaque secret — not as a value your code should parse or branch on.
bash
# Sign in and get a JWT
curl -X POST https://api.supero.dev/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"password": "secret123",
"domain": "acme-corp"
}'
# Use the JWT on a CRUD call
curl https://api.supero.dev/api/v1/crud/acme-corp/appointment \
-H "Authorization: Bearer <access_token>"
# Or use a service API key instead
curl https://api.supero.dev/api/v1/crud/acme-corp/appointment \
-H "X-API-Key: ak_..."An expiring access token is refreshed with POST /api/v1/auth/refresh (exchange the refresh_token for a new access_token) and a session is ended early with POST /api/v1/auth/logout, which revokes it immediately rather than waiting for expiry.
API Keys
An API key is a single opaque secret string — treat the whole thing as a credential, not as a set of parseable, independently meaningful segments. It has the shape below, but your code should never split it apart to make a decision; that's the server's job.
text
ak_{domain}_{project}_{tenant}_{env}_{uuid}_{secret}A key's role comes from the hierarchy scope it was generated for, not from anything a caller supplies at request time. The discriminator is literally whether the project and tenant segments are the default ones:
| Key generated for | Effective role | Access |
|---|---|---|
| default-domain / default-project / default-tenant | platform_admin | Everything — a Supero-staff scope, not one you should be minting |
| Your domain's default-project and default-tenant | domain_admin | Every project and tenant in the domain |
| Any project's default-tenant | project_admin | Every tenant inside that project |
| Any other, named tenant | tenant_user | That tenant only |
⚠️ default-tenant is the line, not the project name
Row three is the one that surprises people: a key minted against any project's default-tenant resolves to project_admin, not tenant_user. If you want a key confined to one tenant's data, mint it against a named tenant you created — never against a default-tenant.
Whichever role a key resolves to, the same RBAC role catalog and — for your own app's schemas — the same object-level access policies apply to it as to a logged-in user with that role. See Access Control (RBAC) and Roles & Permissions Reference for what each role can actually do; a key's scope decides which role it gets, not what that role is allowed to touch.
🚨 A key's scopes list is a cap, never a grant
Beyond its hierarchy scope, a key carries a scopes list (read, write, delete, admin). These only remove verbs from the role the key's address already implies — they can never add one. Any unrecognised token, and any of admin / * / all / full / system, means no restriction at all. Scope enforcement is implemented, but it has been through more than one revision, so an older key or an older deployment may not behave as its scopes suggest. Treat scopes as defence in depth, not as your isolation boundary: the control that actually holds is which tenant the key is issued against. If you intend a key to be read-only, verify it with a write before you rely on it.
⚠️ Two further live limitations on API keys
A key minted for a non-default project or tenant through the domain api-key endpoint currently returns 401 "API key not found or has been deleted" on every call — so the narrowly scoped key you want is also the one most likely not to work yet. And, per the table above, a key issued against any project's default-tenant resolves to project_admin, not tenant_user. Test a new key against a real request before you depend on it, and read its resolved scope back from the session rather than assuming.
💡 Default to the narrowest scope that works
Generate tenant-scoped keys for anything that only needs to touch one tenant's data, and reserve domain- or project-level keys for administrative tooling. A leaked tenant-scoped key exposes one tenant; a leaked domain-level key exposes everything in the domain.
⚠️ Key handling
The full key is shown once, at creation — store it in a secrets manager or environment variable immediately. Never commit an API key to source control. To rotate a key without downtime, create a new key with the same scope, roll it into your app's configuration, then disable or delete the old one once the new one is confirmed working.
Data Isolation
Every request is scoped by the domain — and, within it, the project and tenant — carried in your JWT or API key. There is no cross-domain query capability: a credential issued for one domain cannot address another domain's records, regardless of what path or filter it sends. This holds the same way whether your domain's data lives on Supero's MongoDB or PostgreSQL backend.
Within a domain, who can see which projects and tenants is mostly a property of role scope (domain-wide roles see everything in the domain; project-scoped roles see one project; tenant-scoped roles see one tenant) — with one important exception: membership of a tenant named default-tenant grants project-wide cross-tenant access on the tenant name alone, with no role check, so customers should never be provisioned into it, and which individual rows and fields a role can see within its own scope is governed by object-level access policies — including owner-scoping, where a rule can restrict a role to only the rows tied to the caller's own identity. See Projects & Tenants for the full domain/project/tenant hierarchy and Access Control (RBAC) for how record- and field-level restriction works.
Field-Level Protection
Supero doesn't have a schema flag that transparently encrypts a field for you server-side — there's no "encrypted": true property you add to a field. If a value needs protection beyond normal transport and access control, you encrypt it yourself, client-side, with a key you generate and hold: the client library supports enabling encryption on API calls so that field values are encrypted before they ever leave your process and decrypted again when you read them back. A small, fixed set of system fields (the record's uuid, name, fq_name, and parent linkage) always stays in plaintext, because Supero needs them to route, link, and validate the record.
Separately, when you configure a managed integration from the services catalog, the credential blob you enter (for example, a payment or messaging provider's API secret) is sealed at rest — but only when the platform key store is enabled on that deployment. Sealing is driven by an explicit object-type registry that covers exactly two types: a service_config's config_data, and a platform_secret's secret_blob. Note the scope: what gets sealed is the whole config_data blob for the object type, not a hand-picked subset of credential fields inside it.
🚨 Sealing is conditional, not automatic
At-rest sealing requires the deployment's key store to be enabled and a key provisioned. It is not on by default, and when the key store is off, sealing is a pass-through that returns the value unchanged — no error, no warning, and the record looks identical through the API. So on a self-hosted or on-premise deployment where the key store was never turned on, every integration credential you have configured is stored in plaintext, and nothing in the product will tell you. Confirm the key store is enabled before you put a real vendor credential into a service config; see On-Premise Deployment for how to turn it on, or contact [email protected]. On Supero Cloud the key store is managed for you. Note also that an "encrypted": true flag on a service-catalog manifest field controls response masking only — it has never controlled at-rest sealing, so finding it in a manifest is not evidence that anything is encrypted.
Neither mechanism is a general one you can point at your own schema, which is what the client-side approach above is for.
💡 Full walkthrough
See the Field Encryption page for the exact client setup, what stays queryable versus not, and key-management guidance — there is no key recovery if you lose your own key.
Audit Logging
Status on Supero Cloud: the general activity audit (authentication, admin actions, CRUD operations) is being rebuilt so that every entry names the user who acted, and the agent if one acted for them; until that ships it is not recording on Supero Cloud. Staff access to your domain (Supero support switching into it) is audited today, and that audit fails closed: the switch is refused if the audit entry cannot be written. On self-hosted deployments the audit log can be enabled by the operator. When enabled, entries are audit_log records scoped to your domain with the format below, readable through the standard CRUD read/list endpoints using the audit_log object type.
| Field | Description |
|---|---|
| event_type | What happened — e.g. auth_success, auth_failure, admin_action, schema_operation, crud_operation, rate_limit_exceeded |
| severity | info, warning, error, or critical |
| timestamp | When the event occurred, in UTC |
| actor | The API key prefix used for the request (per-user attribution is part of the rebuild) |
| target_type / target_resource | The kind of object and the specific record the event touched |
| action | The operation performed, for CRUD and admin events |
| client_ip / user_agent | Where the request came from |
| details | Additional context for the event |
Retention is set by the deployment operator.
Security Checklist
- •Scope API keys as narrowly as the task needs — prefer a tenant-scoped key over reusing a domain-wide one everywhere.
- •Store keys and tokens in a secrets manager or environment variables, never in source control.
- •Configure access policies for any role that shouldn't default to full access — the platform default is permissive (viewer defaults to read-only, every other role defaults to full access) until you configure otherwise.
- •Use owner-scoping (filter_field / filter_match on an access rule) for any entity where a tenant user should only see their own rows.
- •Enable client-side field encryption for values that need protection beyond normal access control, and keep the encryption key out of source control.
- •Disable or delete API keys you no longer use instead of leaving them active indefinitely.
- •On an on-premise deployment, secure your MongoDB instance as part of setup — enable authentication and restrict network access to it — before putting real data on it.
Next Steps
- •Access Control (RBAC) — the full two-layer authorization model: the role catalog and object-level access policies
- •Roles & Permissions Reference — every built-in role, custom roles, and the sign-in/account flows that put a role on a session
- •Field Encryption — client-side field encryption setup, limits, and key-management guidance
- •Projects & Tenants — the domain/project/tenant hierarchy your isolation boundary is built on
On this page