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: 419 ≡
refresh: { 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.