Skip to content
Settings

auth.tokens — /oauth/token grants and JWT claim contract

cyoda-go version 0.8.2

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.

Admin (cyoda operator) sets up:

  • CYODA_IAM_MODE=jwt
  • CYODA_JWT_SIGNING_KEY (PEM RSA private key; tokens cyoda issues are signed with this)
  • CYODA_JWT_ISSUER (default cyoda; populates the iss claim)
  • CYODA_JWT_AUDIENCE (default empty = no aud check on inbound tokens)
  • CYODA_JWT_EXPIRY_SECONDS (default 3600)
  • CYODA_JWT_BOOTSTRAP_AUDIENCE (default client; controls which key signs M2M tokens)

See config.auth for the full env-var reference.

Client (you) needs:

  • For client_credentials: a registered M2M client_id/secret (see auth.clients).
  • For token-exchange (OBO): an already-valid subject JWT plus an M2M client_id/secret to act as the actor.

Mint an M2M JWT with your client credentials:

Terminal window
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.

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).

Terminal window
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_id must match the M2M client’s tenant. Tenant mismatch → 403 access_denied.
  • The issued OBO token carries sub = the subject’s sub, user_roles from the subject token, and an act claim {"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_id for M2M, user ID for OBO and federated tokens.
  • iss (string) — Issuer. Cyoda-minted tokens use CYODA_JWT_ISSUER. Federated tokens use the upstream IdP’s issuer.
  • aud (string or string array) — Audience. Checked against CYODA_JWT_AUDIENCE if set; against expectedAudiences for 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 duplicates sub (= client_id).
  • user_roles (string array) — Roles granted (e.g. ROLE_ADMIN, ROLE_M2M). Federated OIDC tokens carry roles from the provider’s configured rolesClaim (default roles; per-provider override available — see auth.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 on client_credentials tokens.

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.UNAUTHORIZED (401) — Authorization header missing, token expired, signature invalid, issuer untrusted, or kid not 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) — malformed grant_type, missing form fields, invalid subject_token shape.
  • The /oauth/token endpoint 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.
  • auth.clients — provision the M2M client used by client_credentials and OBO
  • auth.oidc — federate an external IdP whose JWTs cyoda will accept directly
  • auth.trusted-keys — register a public key so JWTs you sign offline are accepted
  • config.authCYODA_JWT_*, CYODA_BOOTSTRAP_*
  • openapicyoda help openapi tags and look for the IAM tag
  • 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 the client_credentials grant.
  • 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/token round-trip.
  • cyoda help config auth — config.auth — IAM mode, JWT issuer, HMAC secret, and admin bootstrap controls.
  • cyoda help errors UNAUTHORIZED — Returned when the Authorization header 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, admin is required for administrative operations). Tenant mismatch — where the caller’s tenant does not match the resource — also produces this error.