Authenticate with OIDC

Let CI jobs and deployments use their provider's OIDC ID token instead of a stored API key.

GitHub Actions, Vercel, GitLab, CircleCI and most other CI and hosting platforms give each job or function a short-lived, signed OIDC ID token that says who it is: which repository, branch, project or environment. An environment's OIDC credential trusts those tokens under conditions you set, and exchanges a matching one for a coss.com access token that works like an API key for an hour at most. Nothing long-lived is stored in your CI or hosting settings, so there is no key to leak or rotate.

Create a credential

On the environment's API keys page, under OIDC credentials, choose Add OIDC credential (owners and admins). Pick the provider and say what to trust:

A credential is an organization key without a secret: choose its access (full, sending only, read only or custom) like a key's, and how long its access tokens last (5 minutes to an hour). Its spend, budget, logs and audit events are the credential's, by its name.

Pinning. A repository or project name can be deleted and registered again by someone else. So the first token exchanged records the provider's ids for it, and later tokens must carry the same ids. After moving a repository on purpose, Reset pins on the credential.

Each credential has an id (oidc_…). It isn't a secret: keep it in your workflow file or project settings.

Use it

The SDK, the CLI and the MCP server use COSS_OIDC_CREDENTIAL_ID when no COSS_API_KEY is set. They find the job's ID token on their own: COSS_OIDC_TOKEN if set, otherwise GitHub Actions' token endpoint, Vercel's (the function's request, else VERCEL_OIDC_TOKEN) or CircleCI's (CIRCLE_OIDC_TOKEN_V2).

import {  } from "coss";

// Exchanges the ID token on the first request, and again before the access token expires.
const  = new ({ : { : "oidc_01k7c8m4q2f3e9v6t5r1n0p8yz" } });

const { ,  } = await ..({
  : "Acme <deploys@acme.com>",
  : "team@acme.com",
  : "Deployed",
  : "main is live.",
});

Pass token to read the ID token yourself (token: () => readToken()), and audience when GitHub Actions should issue it for another audience than https://coss.com.

GitHub Actions

The job needs permissions: id-token: write:

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    env:
      COSS_OIDC_CREDENTIAL_ID: oidc_01k7c8m4q2f3e9v6t5r1n0p8yz
    steps:
      - run: npx coss-cli emails send --from deploys@acme.com --to team@acme.com --subject "Deployed" --text "main is live."

Vercel

Turn on OIDC Federation in the project's settings (the issuer mode you pick there must match the credential's), then set COSS_OIDC_CREDENTIAL_ID. Functions get a fresh token with each request and builds get VERCEL_OIDC_TOKEN; the SDK reads both.

GitLab

Ask for an ID token for https://coss.com as COSS_OIDC_TOKEN:

deploy:
  id_tokens:
    COSS_OIDC_TOKEN:
      aud: https://coss.com
  variables:
    COSS_OIDC_CREDENTIAL_ID: oidc_01k7c8m4q2f3e9v6t5r1n0p8yz
  script:
    - npx coss-cli emails send --from deploys@acme.com --to team@acme.com --subject "Deployed" --text "main is live."

Anything else

Exchange the token yourself with POST /v1/oidc/token (no API key needed) and use the access_token as a bearer key:

curl -X POST https://api.coss.com/v1/oidc/token \
  -H "Content-Type: application/json" \
  -d '{"credential_id": "oidc_01k7c8m4q2f3e9v6t5r1n0p8yz", "token": "'"$ID_TOKEN"'"}'

DELETE /v1/api_key with an access token revokes just that token, for a job that wants to sign out when it's done. Deleting the credential revokes all of its tokens at once.

When a token is refused

Every refusal answers 401 invalid_oidc_token, whatever the reason, so a token from someone else's repository learns nothing about your conditions. The reason is in the environment's logs: the request line of POST /v1/oidc/token carries auth_failure, such as audience_mismatch, condition_failed:repository, pinned_claim_mismatch:repository_id, expired or issuer_unreachable.

coss.com verifies each token's signature against the issuer's published keys (RSA, ECDSA or EdDSA only), its exact issuer and audience, its expiry, then every condition and pinned claim.

Last updated