Introduction
Issues and verifies the JWT access tokens used across the UpStart Commerce platform — for shoppers browsing a storefront, for staff using management tools, and for the services that trust those tokens.
Which half you are building for changes what you do with a token:
- Consumer traffic — storefronts, apps. Verify the signature locally and read the claims. No call back to this service, and no permission lookup.
- Management traffic — admin and back-office tools. Verify the same way, then resolve authorization against profile-permissions-svc. Because the token carries role references rather than a permission list, granting a role takes effect immediately, without reissuing anyone's token.
Verifying a token
Fetch the signing keys from /.well-known/jwks.json. It needs no credentials, sends CORS headers, and is cacheable for 24 hours — fetch once, cache, and refetch only when you meet a kid you do not recognise.
Verify RS256, then check that exp is in the future and that iss matches the issuer configured for the environment you are calling — auth-svc by default, but it is a configurable value, so read it from your own config rather than hard-coding the literal. The claims you get back:
Claim | Meaning |
|---|---|
sub | User id |
type | consumer or management — which model above applies. Lower-case on the wire; compare case-sensitively. |
tenant | The tenant this session is active for |
roles | Global and tenant-level role names |
siteRoles | Site id → role names, for site-scoped access |
iat / exp | Issued-at and expiry, as Unix timestamps |
iss | The issuer configured for the environment. auth-svc unless overridden. |
Tokens are deliberately thin (~500 bytes). Everything above is in the token; anything not in the token needs a lookup.
Getting a token
Anonymous browsing needs no credentials:
curl -X POST https://api.upstartcommerce.com/api/v1/auth/guest \
-H 'x-upstart-tenant: your-tenant'Signing a user in returns an access and refresh token:
curl -X POST https://api.upstartcommerce.com/api/v1/auth/login \
-H 'x-upstart-tenant: your-tenant' \
-H 'Content-Type: application/json' \
-d '{"username":"[email protected]","password":"…"}'Then send it on every call:
Authorization: Bearer <accessToken>Login does not always return tokens. If the account requires a second factor you get mfaRequired: true, an mfaChallengeId, the delivery method, a masked destination and the challenge's own expiry — and no tokens. Finish with /auth/mfa/verify, or get a fresh code from /auth/mfa/resend. Treat it as a branch in your login flow, not an error.
Keeping a session alive
Access tokens last one hour and refresh tokens seven days by default, both environment-configurable — read expiresIn from the response rather than hard-coding either.
Refresh rotates. /auth/refresh returns a new pair and invalidates the one you presented, so persist the new refresh token every time. /auth/logout revokes it.
There is also a ceiling on total session age — 30 days by default, and likewise configurable — that refreshing cannot extend. When you reach it the error is session_expired and the only way forward is a full login, so handle it distinctly from an ordinary expired access token.
Tenant and site context
The active tenant travels in the token, as the tenant claim, so a verified token already tells a service which tenant it is acting for.
The x-upstart-tenant header is how a caller selects that tenant when the user belongs to more than one. /auth/tenants lists what the session can reach — use it to drive a tenant switcher — and /auth/refresh moves an existing session to a different tenant without asking for credentials again. /auth/sites does the same for sites within the active tenant.
When calls fail
Errors carry a machine-readable error, a human message and a code. The ones worth branching on:
error | What to do |
|---|---|
unauthorized | Bad credentials or a bad token. Re-authenticate. |
session_expired | Session ceiling reached. Full login; refresh will not help. |
tenant_required | Multi-tenant user, no tenant chosen. Send x-upstart-tenant. |
unauthorized_tenant / unauthorized_site | Authenticated, but not for that scope. |
site_required / no_authorized_sites | Site context missing, or none available to this user. |
account_not_verified | Real account, unverified email or phone. |
account_locked | Too many failed attempts. Temporary. |
rate_limited | Back off and retry. |
invalid_request | Malformed body or a missing field. |