Authentication
Every call your storefront makes to the platform carries two things: which tenant the request is for, and who is asking. Get those two right and the rest of the API behaves predictably.
The header contract
POST /v1/carts HTTP/1.1
Host: api.revenexx.com
X-Revenexx-Tenant: acme
X-Revenexx-Api-Key: <key>
Content-Type: application/json
| Header | Required | What it does |
|---|---|---|
X-Revenexx-Tenant | Always | The tenant slug. This is what scopes the response. |
Authorization: Bearer <token> | One of the two | A token from ID, or a personal access token. |
X-Revenexx-Api-Key | One of the two | A machine-to-machine key, created in Cockpit. |
Two rules follow from the table, and both are absolute:
The tenant header is not optional. A request without it is rejected, not answered across tenants. That's deliberate: the failure mode of a missing tenant should be a 400, not a silent data leak.
You need exactly one credential. A storefront's server routes use X-Revenexx-Api-Key; a request made on a signed-in buyer's behalf carries their bearer token. Don't send both.
Every client points at the single gateway, https://api.revenexx.com, and selects the tenant by slug. There is no per-region host to configure.
The tenant header is what scopes the response
The platform scopes every response to the tenant in the header. You never filter by tenant in application code:
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig(event)
return await $fetch(`${config.revenexxApiUrl}/v1/products`, {
headers: {
'X-Revenexx-Tenant': config.revenexxTenant,
'X-Revenexx-Api-Key': config.revenexxApiKey,
},
})
// Returns only products visible to that tenant
})
Isolation is enforced by the platform, not by your query. Customer A cannot see Customer B's products, and there is no application-level filter you could forget to add.
Credentials are server-side only
api.revenexx.com directly would need the key in the browser — which is precisely why components call /api/* on your own origin instead. See The BFF pattern.export default defineNuxtConfig({
runtimeConfig: {
revenexxApiUrl: 'https://api.revenexx.com',
revenexxTenant: '',
revenexxApiKey: '', // private: server-side only
public: {
// anything here IS sent to the browser
},
},
})
Set the values through environment variables — NUXT_REVENEXX_TENANT, NUXT_REVENEXX_API_KEY — and keep them out of Git. A scaffolded theme ships a .env.example with credential names only.
On a deployed site the platform injects a scoped key for the site's runtime, so you generally don't provision one by hand for production. See Environment variables.
Two different identity systems
id.revenexx.com), the platform's SSO. This page is about the storefront, so it's about buyers. Integrating Cockpit itself is a different topic.The distinction shows up in practice as: a buyer signing in to a storefront never gets a Cockpit session, and a Cockpit user's session grants nothing in the storefront. They're separate identity domains that happen to sit behind the same gateway.
Buyer sign-in in a theme
You don't build password hashing, token signing, or a session store. The base layer ships the buyer-auth surface — server routes plus the blocks that render them — and your work is the parts that are specific to this customer.
The routes the layer provides, all on your own origin:
| Route | What it does |
|---|---|
POST /api/auth/login | Signs a buyer in and establishes the session |
POST /api/auth/logout | Ends the session |
GET /api/auth/me | The current buyer, or nothing |
POST /api/auth/register | Creates a buyer account |
GET /api/auth/registration-config | What the registration form should collect for this tenant |
POST /api/auth/recovery | Requests a password-reset link |
PUT /api/auth/recovery | Completes the reset with the token from the link |
And the blocks that render them, so a login page is something an editor composes rather than something you build: login_form, register_form, forgot_password_form, reset_password_form. See the block catalog.
Like every other domain, these resolve through a service key — authService — which defaults to mock. You can build and style the entire sign-in experience against fixtures before a real identity provider is involved.
Where sessions live
A buyer's session is a server-set httpOnly cookie. That's the right default and worth understanding rather than changing:
- JavaScript cannot read it, so an XSS bug does not hand over the session.
- The browser attaches it automatically, so your
$fetchcalls need no header plumbing. - The cost is that cookie-based auth needs CSRF protection on state-changing requests — which you want anyway.
The alternative — keeping a token in localStorage and attaching it as a bearer header — is simpler to reason about but readable by any script on the page. Don't reach for it in a storefront.
Extending the flows
Every B2B project extends at least one part of buyer identity: tying registration to a customer-approval workflow, restricting sign-up to invited users, adding enterprise SSO for one customer, or supporting magic-link sign-in.
The extension point is your own server route. Shadow the layer's route to replace it, or add a route beside it and point your block at the new one — see Overriding the base layer. What you should not do is reimplement the credential handling; the platform owns that.
GET /api/auth/registration-config is the seam to look at first. Registration requirements vary per customer far more than the sign-in flow does, and driving the form from configuration is cheaper than forking it.
Security practices that carry the weight
A handful of habits, in rough order of how often they're the thing that was missing:
- HTTPS everywhere. Every deployment gets TLS automatically — see Domains.
httpOnlycookies, plus CSRF protection on state-changing requests.- Identical failure messages. "Invalid email or password" for both a wrong password and an unknown address; "check your email" whether or not the account exists. Anything more specific tells an attacker which addresses are real.
- Rate-limit sign-in attempts to blunt brute-force and credential-stuffing. The gateway's own limits are documented in Rate limits; apply your own on your routes too.
- Set the secure response headers — HSTS, CSP,
X-Frame-Options. - Never log credentials or bodies wholesale. Request and response detail, including headers, is captured in request logs. What you never write is the only thing you never have to expire.
Next steps
- The BFF pattern — where the credential lives and why.
- Multi-tenant themes — resolving the tenant per request when one deployment serves many.
- Auth — the platform's identity service, sessions, API keys and OAuth2.
- API usage — errors, rate limits, pagination, idempotency and versioning.
- Catalog, cart and checkout — the surfaces a signed-in buyer uses.