Tokens
The JWT model — short-lived access tokens, audiences per product, the refresh flow, and JWKS.
Every Norcube API accepts Bearer JWTs. The dashboard and the CLI manage the whole lifecycle for you; this page is for anyone integrating directly.
The model
Two token kinds, both RS256-signed JWTs:
| Token | Lifetime | Where it lives |
|---|---|---|
| Access token | ~5 minutes | Sent as Authorization: Bearer <jwt> on every request. Claims include the user (sub), the organization_id, the aud list, and token_type: "access". |
| Refresh token | 30 days, renewed on every use | The session. For the web app: an HTTP-only jds_refresh_token cookie. For the CLI: stored per profile in the OS keyring, or in a file readable only by you where there is no keyring. Used only to mint new access tokens. |
Access tokens are deliberately short — clients should treat
401 INVALID_TOKEN as "refresh once and retry", not as an error.
Audiences
Each product validates tokens against its own audience; a token minted for one product won't authenticate against another:
| Audience | Service |
|---|---|
auth | The auth service itself (user profile, organizations, IAM). |
langsync-api | LangSync |
snapdb-api | Backup |
domainradar | DomainRadar |
billing | Billing |
prompthub | PromptHub |
Convenience: tokens minted for a product audience also include auth,
so the same token can fetch your profile and org list alongside product
calls.
Minting an org-scoped access token
The auth service exposes an OAuth2-style endpoint. The refresh token
travels as the jds_refresh_token cookie (not a form field):
curl -X POST "https://auth.api.norcube.com/oauth/token" \
-H "Cookie: jds_refresh_token=<your-refresh-token>" \
-d grant_type=refresh_token \
-d audience=langsync-api \
-d organization_id=<org-uuid>The response carries a fresh access token (and possibly a rotated refresh token — always persist the returned one). Cache the access token until shortly before expiry, then refresh.
Two things the endpoint does not do:
- No
client_credentialsgrant. There is no machine-to-machine client flow; the refresh token always originates from a human sign-in. - For unattended access, use a CI token: an
organization-scoped, expiring credential exchanged for 5-minute
product tokens. Its
subis the CI token's id (withprincipal_type: "ci_token"), not a person, and itsscopeclaim limits it to what it was granted. There are no personal access tokens.
CLI sessions and refresh-token families
A CLI sign-in (POST /auth/cli/exchange) is recorded as a session, and
the session is one refresh-token family:
- Every refresh token the session issues, and every access token minted
from them, carries the session id in a
sidclaim. - Only the newest refresh token of the family works. Each refresh
replaces it; exactly one of two simultaneous refreshes of the same
token succeeds, the other gets
401. - Presenting an older refresh token of the family means two parties
hold the session's tokens, so the whole session is revoked
(RFC 9700 §4.14.2).
The token replaced in the last 10 seconds is the one exception: it is
refused with
401, but the session survives, because that is a client retrying a request whose response it lost. POST /auth/cli/sign-outwith the form fieldrefresh_tokenends the session the token belongs to. It always answers204.
A device sign-in (nrc login --device, the OAuth 2.0 Device
Authorization Grant of RFC 8628)
ends in exactly the same kind of session. The CLI asks
POST /auth/cli/device/code for a code pair, the person approves the
user code on the dashboard's /device page, and the CLI's polling of
POST /auth/cli/device/token (errors authorization_pending,
slow_down, access_denied, expired_token) then returns the session,
once.
Web sign-in tokens carry no sid and aren't listed as sessions.
Verifying tokens (JWKS)
The signing keys are public:
GET https://auth.api.norcube.com/.well-known/jwks.jsonIf you're building something that receives Norcube tokens, verify
RS256 signatures against this JWKS and check the aud and
token_type claims — exactly what every Norcube service does.
Behaviour and edge cases
- Wrong audience →
401. Mint a token with the audience of the API you're calling. - Refresh tokens can rotate. When
/oauth/tokenreturns a new refresh token, the old one is dead — store the replacement immediately (the CLI does this for you). - Sessions are independent. Web login and CLI login hold separate refresh tokens; signing out of one doesn't invalidate the other.
- CLI sessions can be revoked. Each CLI sign-in is a session listed
under Account › CLI sessions in the dashboard (and
GET /auth/me/sessions). Revoking one stops its refresh token at once; access tokens it already minted run out within their ~5-minute lifetime. Sessions unused for 30 days expire. - Lost refresh token = re-login. They can't be re-issued.