Release candidate — 1.0.0-rc.7
StitchAPI
GuidesAuth

oauth2

OAuth2 client_credentials: fetch, cache, and auto-refresh a token behind the capability boundary.

Use oauth2 when an API authenticates with the OAuth2 client_credentials grant and you want the access token fetched, cached, and refreshed for you — the stitch holds the client id and secret, the caller never sees the token.

Example

import {  } from 'stitchapi';
import { ,  } from 'stitchapi/auth';

const  = ({
    : 'https://api.example.com',
    : '/orders',
    : ({
        : 'https://api.example.com/oauth/token',
        : ('CLIENT_ID'),
        : ('CLIENT_SECRET'),
        : 'orders.read',
    }),
});

The first call to orders() POSTs the form-encoded token request, caches the token in the stitch store (TTL from expires_in), and attaches it as Authorization: Bearer …; later calls reuse the cached token until it nears expiry, all behind the capability boundary.

Options

tokenUrl is the client_credentials token endpoint. Pass clientId and clientSecret as secret resolvers — e.g. env('CLIENT_ID') — so the credentials are read at call time and never committed. scope is an optional space-delimited scope string.

The token is cached and auto-refreshed through one refresh envelope (CONTRACT.md P24). A bare StatusMatch — a status, a list, or a predicate — is shorthand for refresh: { on }: the status(es) that mean the token was rejected and force a fresh fetch plus retry (default [401], and refresh: 419refresh: { on: [419] }). Reach refresh.skew — how many milliseconds before expiry to refresh so the token is never used mid-flight (default 30_000) — through the envelope form: refresh: { skew: '1m' }.

Give two stitches the same key plus a shared store and one token serves them all — across stitches and across workers, surviving restarts. See Reference → Auth strategies for every field.

This strategy covers client_credentials, where the token is yours to re-fetch at will. A provider that issues single-use refresh tokens is a different problem — two workers refreshing at once can revoke the whole grant, and the obvious guards turn out to be narrower races rather than fixes. OAuth2 refresh tokens that rotate builds the strategy that holds.

Client authentication

By default the client authenticates with client_secret_post: client_id and client_secret ride in the form body. Some providers require client_secret_basic instead — the credentials as an HTTP Basic header, with only grant_type in the body. Set clientAuth: 'basic' for those:

import {  } from 'stitchapi';
import { ,  } from 'stitchapi/auth';

const  = ({
    : 'https://sms.example.com',
    : '/messages',
    : ({
        : 'https://sms.example.com/oauth/token',
        : ('CLIENT_ID'),
        : ('CLIENT_SECRET'),
        : 'basic',
    }),
});

The Basic header is Base64 of id:secret, encoded without Node's Buffer so it works in a browser bundle too. For provider-specific extras on the token request, audience sets the OAuth2 audience field, params merges arbitrary fields into the token-request body (e.g. resource, or a custom grant_type), and headers adds headers to it. The client credentials are always applied last, so params can never shadow them.

Anti-pattern: don't rely on the default token cache across multiple workers — it lives in memory, process-local, so every worker fetches and refreshes its own token and hammers the token endpoint; give the stitches a shared key and a durable store so one token serves them all. See Pluggable store.

The token lives only in the store and the outgoing Authorization header — the caller, an agent included, never receives it. That is the capability boundary; see Capability, not credential.

See also

On this page