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';
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.
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';
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.