cookieSession
Log in once, capture and replay cookies, and refresh the session on a status code or a soft 200 wall.
Use cookieSession when an API authenticates with a login form and a cookie
instead of a token, and you want the session captured, replayed, and refreshed
for you — the stitch holds the login, the caller never touches a cookie.
Example
import { , , } from 'stitchapi';
// The login is itself a stitch — its Set-Cookie response seeds the session.
const = ({
: 'POST',
: 'https://api.example.com',
: '/login',
: 'form',
});
const = ({
: 'https://api.example.com',
: '/me',
: ({
,
: '*',
: () => ({
: { : ('USER')(), : ('PASS')() },
}),
: [401],
}),
});The first call to me() runs login once, captures its cookies, and replays
them; a 401 afterward re-runs the login and retries, all behind the
capability boundary.
Options
Point login at the stitch that authenticates, and pass loginInput to supply
its credentials, resolved at call time with env().
cookie selects what to keep: a single cookie name, or '*' (equivalently
jar: true) to capture and replay the whole Set-Cookie jar.
refresh is one envelope (CONTRACT.md P24). A bare StatusMatch — a number, a
list, or a predicate — is shorthand for refresh: { on }: the status(es) that
re-run the login (default [401], and refresh: 401 ≡ refresh: { on: [401] }).
Reach refresh.when — a content predicate for soft 200 walls where a login page
is served with a 200 — through the envelope form: refresh: { when: (res) => … }. Give two stitches the same key plus a shared store to
share one session. See
Reference → Auth strategies for every field.
Durable state and an external recovery loop
cookieSession does the mechanical capture-and-replay, refreshing on the wall —
but it can't model a host that owns durable, categorised auth state and its
own recovery loop (mark a session backoff after a rate-limit, failed after
bad creds, and retry on its own schedule). Two optional host-owned hooks hand
that decision back to you. They fire once per actual login attempt — inside
the single-flight refresh, never once per coalesced caller — and a throwing hook
never crashes the call.
onRefresh({ ok, status })runs after every (re)login attempt with its outcome (ok= a cookie was captured), so you can persist session state and clear or extend a cooldown.onAuthFailure(info)runs when an attempt captured no cookie, with acategoryyou map to your own status:'unauthenticated'(arefreshstatus, e.g.401— bad/expired creds),'rate-limited'(429, withretryAfterparsed fromRetry-After),'network'(the login threw before any response — with the thrownerror), or'unknown'.info.phaseis'apply'for a cold session or'refresh'for a wall that was hit mid-stream.
import { , , } from 'stitchapi';
import type { AuthFailureResult, RefreshResult } from 'stitchapi';
// Your own durable store — a row in a database, a Redis hash, anything.
declare const : {
(): <void>;
(: string): <void>;
(: number): <void>;
};
const = ({
: 'POST',
: 'https://api.example.com',
: '/login',
: 'form',
});
const = ({
: 'https://api.example.com',
: '/me',
: ({
,
: '*',
: () => ({
: { : ('USER')(), : ('PASS')() },
}),
// Persist the outcome of every attempt.
: async ({ }: RefreshResult) => {
if () await .();
},
// Categorise a failure and drive your own recovery loop.
: async (: AuthFailureResult) => {
switch (.) {
case 'rate-limited':
// Back off until the server says we may retry.
await .(
.() + (. ?? 60_000),
);
break;
case 'unauthenticated':
// Bad/expired creds — stop hammering; surface for re-auth.
await .('credentials rejected');
break;
case 'network':
// Transport failure — your loop decides whether to retry.
await .(.() + 30_000);
break;
default:
await .('unknown auth failure');
}
},
}),
});The hooks are purely additive: omit them and cookieSession behaves exactly as
before. They observe and record — they never change whether the stitch logs in
or retries (that stays governed by refresh). Your external loop
reads the durable state and decides when to call the stitch again.
Anti-pattern: don't lean on refresh.on status codes alone to catch an
expired session — a soft 200 wall, where the login page comes back with
status 200, slips straight past it and your stitch reads the login HTML as
data; add a refresh.when content predicate so the session re-logs in on
that body too. See STITCH_AUTH_WALL.