auth.tokens — /oauth/token grants and JWT claim contract
cyoda-go version 0.8.2
auth.tokens
Section titled “auth.tokens”auth.tokens — exchange credentials for a JWT at POST /api/oauth/token. Covers every supported grant and the canonical JWT claim contract that all cyoda tokens (M2M, OBO, federated OIDC, trusted-key) conform to.
You have a way to prove identity (an M2M client_id/secret, or a JWT minted by a federated IdP, or a JWT you signed offline) and you want a cyoda-issued (or cyoda-validated) JWT to present on subsequent API calls.
This is the single home for the JWT claim contract. auth.oidc and auth.trusted-keys link here for claim shape.
PREREQUISITES
Section titled “PREREQUISITES”Admin (cyoda operator) sets up:
CYODA_IAM_MODE=jwtCYODA_JWT_SIGNING_KEY(PEM RSA private key; tokens cyoda issues are signed with this)CYODA_JWT_ISSUER(defaultcyoda; populates theissclaim)CYODA_JWT_AUDIENCE(default empty = noaudcheck on inbound tokens)CYODA_JWT_EXPIRY_SECONDS(default3600)CYODA_JWT_BOOTSTRAP_AUDIENCE(defaultclient; controls which key signs M2M tokens)
See config.auth for the full env-var reference.
Client (you) needs:
- For
client_credentials: a registered M2Mclient_id/secret(seeauth.clients). - For token-exchange (OBO): an already-valid subject JWT plus an M2M
client_id/secretto act as the actor.
REQUEST FLOW
Section titled “REQUEST FLOW”client_credentials — most common
Section titled “client_credentials — most common”Mint an M2M JWT with your client credentials:
curl -X POST https://cyoda.example.com/api/oauth/token \ -u "${CLIENT_ID}:${CLIENT_SECRET}" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials"Response (200 OK):
{ "access_token": "eyJhbGciOiJSUzI1NiIs…", "token_type": "Bearer", "expires_in": 3600}Use the access_token as Authorization: Bearer … on every subsequent API call. Mint again when it nears exp; cyoda does not issue refresh tokens.
token-exchange (OBO)
Section titled “token-exchange (OBO)”You are an M2M actor (e.g. a backend service) and you want to call cyoda on behalf of a user whose token you already hold. The OBO grant re-signs the subject token so cyoda sees the user as the principal and your service as the actor (RFC 8693).
curl -X POST https://cyoda.example.com/api/oauth/token \ -u "${CLIENT_ID}:${CLIENT_SECRET}" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \ -d "subject_token=${USER_TOKEN}" \ -d "subject_token_type=urn:ietf:params:oauth:token-type:jwt"Response shape matches client_credentials plus a issued_token_type field:
{ "access_token": "eyJhbGciOiJSUzI1NiIs…", "token_type": "Bearer", "expires_in": 3600, "issued_token_type": "urn:ietf:params:oauth:token-type:jwt"}Key constraints:
- The subject token’s
caas_org_idmust match the M2M client’s tenant. Tenant mismatch →403 access_denied. - The issued OBO token carries
sub= the subject’ssub,user_rolesfrom the subject token, and anactclaim{"sub": "<m2m client_id>"}identifying the actor. - Subject token must already be valid (signature, not expired).
Cyoda-minted tokens (issued via client_credentials or token-exchange/OBO at /oauth/token, or via the bootstrap key in auth.trusted-keys) carry the following claim shape. Federated OIDC tokens (auth.oidc) are not re-minted; they carry the upstream IdP’s claim shape, and tenant + user identity are bound server-side from the registered provider’s OwnerLegalEntityID — claims like caas_org_id, caas_user_id, tid on a federated token are explicitly ignored to prevent attacker-controlled tenant routing.
Claim shape for cyoda-minted tokens:
sub(string) — Principal.client_idfor M2M, user ID for OBO and federated tokens.iss(string) — Issuer. Cyoda-minted tokens useCYODA_JWT_ISSUER. Federated tokens use the upstream IdP’s issuer.aud(string or string array) — Audience. Checked againstCYODA_JWT_AUDIENCEif set; againstexpectedAudiencesfor federated providers.exp(int unix) — Expiry.iat(int unix) — Issued-at.jti(string UUID) — Unique token ID.caas_org_id(string UUID) — Tenant scope. Every API call is constrained to this tenant.caas_user_id(string) — User identifier. For M2M tokens this duplicatessub(=client_id).user_roles(string array) — Roles granted (e.g.ROLE_ADMIN,ROLE_M2M). Federated OIDC tokens carry roles from the provider’s configuredrolesClaim(defaultroles; per-provider override available — seeauth.oidc).caas_tier(string) — Tier label. cyoda-go: always"unlimited"; Cloud distinguishes paid tiers.act(object) — OBO only.{"sub": "<m2m client_id>"}identifying the M2M actor that exchanged the user token. Absent onclient_credentialstokens.
Cyoda issues tokens signed with CYODA_JWT_SIGNING_KEY (RS256). The kid header points at the active keypair in the keystore (/oauth/keys/*). Federated OIDC tokens are validated against the registered provider’s JWKS — never signed by cyoda. Trusted-key tokens are signed by you (offline) with the matching private key for a registered public key.
ERRORS
Section titled “ERRORS”errors.UNAUTHORIZED(401) —Authorizationheader missing, token expired, signature invalid, issuer untrusted, orkidnot in any registered keystore / OIDC JWKS / trusted-key registry.errors.FORBIDDEN(403) — token valid but caller lacks the required role for the operation.errors.BAD_REQUEST(400) — malformedgrant_type, missing form fields, invalidsubject_tokenshape.- The
/oauth/tokenendpoint returns OAuth-shaped errors ({"error": "...", "error_description": "..."}) per RFC 6749 rather than the generic cyoda error envelope —invalid_client,invalid_grant,access_denied,server_error.
SEE ALSO
Section titled “SEE ALSO”auth.clients— provision the M2M client used byclient_credentialsand OBOauth.oidc— federate an external IdP whose JWTs cyoda will accept directlyauth.trusted-keys— register a public key so JWTs you sign offline are acceptedconfig.auth—CYODA_JWT_*,CYODA_BOOTSTRAP_*openapi—cyoda help openapi tagsand look for theIAMtag
See also
Section titled “See also”cyoda help auth— auth — authenticate client applications against cyoda.cyoda help auth clients— auth.clients — provision and manage machine-to-machine (M2M) clients that authenticate against cyoda via theclient_credentialsgrant.cyoda help auth oidc— auth.oidc — register an external OIDC identity provider so JWTs it issues are accepted by cyoda directly, without re-minting at/oauth/token.cyoda help auth trusted-keys— auth.trusted-keys — register a public key with cyoda so JWTs you sign offline with the matching private key are accepted, without an IdP or/oauth/tokenround-trip.cyoda help config auth— config.auth — IAM mode, JWT issuer, HMAC secret, and admin bootstrap controls.cyoda help errors UNAUTHORIZED— Returned when theAuthorizationheader is missing, the bearer token is expired, the token signature is invalid, or the token was issued by an untrusted issuer. Also returned when a request reaches a protected route with no identity context established by the auth middleware.cyoda help errors FORBIDDEN— The request was authenticated successfully but the caller’s JWT claims do not include the role required by the endpoint (for example,adminis required for administrative operations). Tenant mismatch — where the caller’s tenant does not match the resource — also produces this error.
Raw formats
Section titled “Raw formats”/help/auth/tokens.json— full descriptor (matchesGET /help/{topic}envelope)/help/auth/tokens.md— body only