-
Notifications
You must be signed in to change notification settings - Fork 0
Session Auth
Added in v1.5.0
Many APIs require session-based authentication: you first prove your identity (e.g., with Basic Auth or a login endpoint), receive session cookies, and then use those cookies on every subsequent request. Some APIs also require a CSRF token on mutating requests (POST, PUT, DELETE) to guard against cross-site request forgery.
SessionAuthStrategy wraps any base auth strategy to handle this automatically. It manages the full session lifecycle -- establishing sessions, injecting cookies, mirroring cookie values to custom headers (for CSRF), scoping cookies and headers to specific HTTP methods, and refreshing sessions when they expire.
- The base auth strategy authenticates first (e.g.,
BasicAuthStrategyproduces anAuthorization: Basic ...header). -
SessionAuthStrategysends a request to a configurable session endpoint using those base credentials. - Named cookies are extracted from the
Set-Cookieresponse headers. - On subsequent API requests, extracted cookies are assembled into a
Cookieheader and sent along. - Optionally, cookie values are mirrored to custom request headers (e.g., an
XSRF-TOKENcookie value copied to anX-XSRF-TOKENheader). - Cookies and mirrored headers can be scoped to specific HTTP methods (e.g., send the CSRF header only on POST/PUT/DELETE).
- If a request receives a response with a status code listed in
refreshOn, the session is automatically refreshed by re-authenticating and hitting the session endpoint again.
The session endpoint request uses redirect: 'manual' to prevent redirect chains from swallowing Set-Cookie headers.
Session auth is configured via the SessionAuthConfig interface:
interface SessionAuthConfig {
session: {
endpoint: string; // Path to the session endpoint, e.g., "/session"
method?: string; // HTTP method (default: "GET")
};
cookies: {
extract: string[]; // Cookie names to capture from Set-Cookie headers
applyTo?: string[]; // HTTP methods to send cookies on; ["*"] for all methods
};
headerMirror?: Array<{
fromCookie: string; // Cookie name to read
toHeader: string; // Header name to write
applyTo?: string[]; // HTTP methods to apply this header
}>;
refreshOn?: number[]; // Status codes that trigger session refresh, e.g., [401]
dropBaseHeaders?: boolean; // Drop base-strategy headers after handshake (e.g., for Spring Security + 2FA)
}session.endpoint -- The path (relative to the base URL) that issues session cookies when called with the base auth credentials. Required.
session.method -- The HTTP method to use when calling the session endpoint. Defaults to "GET".
cookies.extract -- An array of cookie names to capture from the Set-Cookie response headers. Only cookies whose names appear in this array are stored. If this array is empty, a warning is logged at startup.
cookies.applyTo -- An array of HTTP method names (e.g., ["GET", "POST"]) controlling which requests receive the Cookie header. Use ["*"] to send cookies on all requests. If omitted, cookies are sent on all requests.
headerMirror -- An array of rules that copy a cookie value into a custom request header. Each rule has:
-
fromCookie-- the name of the cookie to read from the session -
toHeader-- the header name to set on the outgoing request -
applyTo-- (optional) HTTP methods this header applies to; if omitted, the header is sent on all requests where cookies are also sent
refreshOn -- An array of HTTP status codes. When the API client receives one of these status codes, it automatically refreshes the session (re-authenticating with the base strategy and hitting the session endpoint again), then retries the request.
dropBaseHeaders -- When true, drops the base strategy's headers (e.g., Authorization: Basic … from BasicAuthStrategy) from the resolved session after the /session handshake completes. The base headers are still sent to the session endpoint itself; only post-handshake API calls carry just cookies + any headerMirror headers. Defaults to false. See Dropping base-strategy headers post-handshake below for when this is required.
Pass a sessionAuth config object alongside your auth strategy in createCli():
import { createCli, BasicAuthStrategy } from "@apijack/core";
const cli = createCli({
name: "myapi",
description: "My API CLI",
version: "1.0.0",
specPath: "/v3/api-docs",
auth: new BasicAuthStrategy(),
sessionAuth: {
session: { endpoint: "/session" },
cookies: {
extract: ["SESSION", "XSRF-TOKEN"],
applyTo: ["*"],
},
headerMirror: [
{
fromCookie: "XSRF-TOKEN",
toHeader: "X-XSRF-TOKEN",
applyTo: ["POST", "PUT", "DELETE"],
},
],
refreshOn: [401],
},
});
await cli.run();When sessionAuth is present, createCli() automatically wraps the auth strategy in a SessionAuthStrategy. You do not construct SessionAuthStrategy yourself.
Users of your CLI can override session auth settings per-environment by adding a sessionAuth object to their environment config. Their overrides are deep-merged with the defaults you set in createCli() (see Config Merging below).
When using the apijack standalone CLI, add a sessionAuth object to the environment in ~/.apijack/config.json:
{
"active": "dev",
"environments": {
"dev": {
"url": "http://localhost:3459",
"user": "admin",
"password": "password",
"sessionAuth": {
"session": { "endpoint": "/session", "method": "GET" },
"cookies": {
"extract": ["SESSION", "XSRF-TOKEN"],
"applyTo": ["*"]
},
"headerMirror": [
{
"fromCookie": "XSRF-TOKEN",
"toHeader": "X-XSRF-TOKEN",
"applyTo": ["POST", "PUT", "DELETE"]
}
],
"refreshOn": [401]
}
}
}
}The standalone CLI reads sessionAuth from the active environment config, then passes it to createCli() as if it were a CLI author default.
The Petstore example API demonstrates session auth with CSRF protection end-to-end.
-
GET /sessionrequires HTTP Basic Auth (admin/password). - On success, the server returns two
Set-Cookieheaders:-
SESSION=<token>; Path=/; HttpOnly-- the session cookie -
XSRF-TOKEN=<token>; Path=/-- the CSRF token cookie
-
- All authenticated endpoints require the
SESSIONcookie. - Mutating endpoints (POST, PUT, DELETE) also require an
X-XSRF-TOKENheader whose value matches theXSRF-TOKENcookie. - Sessions expire after 30 minutes.
{
"sessionAuth": {
"session": { "endpoint": "/session", "method": "GET" },
"cookies": {
"extract": ["SESSION", "XSRF-TOKEN"],
"applyTo": ["*"]
},
"headerMirror": [
{
"fromCookie": "XSRF-TOKEN",
"toHeader": "X-XSRF-TOKEN",
"applyTo": ["POST", "PUT", "DELETE"]
}
],
"refreshOn": [401]
}
}A GET request sends only the SESSION cookie:
apijack pets listA POST request sends both the SESSION cookie and the X-XSRF-TOKEN header:
apijack pets create-pet --name "Buddy" --species dog --age 3Use -o curl to inspect the exact headers being sent:
# GET request -- Cookie header only
apijack pets list -o curl
# curl -X GET 'http://localhost:3459/pets' \
# -H 'Cookie: SESSION=abc123; XSRF-TOKEN=def456'
# POST request -- Cookie header + X-XSRF-TOKEN header
apijack pets create-pet --name "Buddy" --species dog --age 3 -o curl
# curl -X POST 'http://localhost:3459/pets' \
# -H 'Content-Type: application/json' \
# -H 'Cookie: SESSION=abc123; XSRF-TOKEN=def456' \
# -H 'X-XSRF-TOKEN: def456' \
# -d '{"name":"Buddy","species":"dog","age":3}'Notice that the X-XSRF-TOKEN header appears only on the POST request, because headerMirror.applyTo is set to ["POST", "PUT", "DELETE"].
When a CLI author provides sessionAuth defaults in createCli() and a user adds sessionAuth overrides in their environment config, the two are deep-merged using these rules:
- Objects are recursively merged. User values override author defaults for matching keys.
-
Arrays are replaced entirely (not concatenated). If a user specifies
cookies.extract, it completely replaces the author'scookies.extractarray. - Scalars are replaced by the user's value.
- If no user override exists, the author's default is used as-is.
The merge is performed by deepMergeSessionAuth() from src/auth/config-merge.ts. Neither the author's config nor the user's config is mutated -- a fresh object is returned via structuredClone.
Author defaults:
sessionAuth: {
session: { endpoint: "/session" },
cookies: { extract: ["SESSION", "XSRF-TOKEN"], applyTo: ["*"] },
headerMirror: [
{ fromCookie: "XSRF-TOKEN", toHeader: "X-XSRF-TOKEN", applyTo: ["POST", "PUT", "DELETE"] },
],
refreshOn: [401],
}User override in environment config:
{
"sessionAuth": {
"session": { "endpoint": "/api/session" }
}
}Result after merge:
{
"session": { "endpoint": "/api/session" },
"cookies": { "extract": ["SESSION", "XSRF-TOKEN"], "applyTo": ["*"] },
"headerMirror": [
{ "fromCookie": "XSRF-TOKEN", "toHeader": "X-XSRF-TOKEN", "applyTo": ["POST", "PUT", "DELETE"] }
],
"refreshOn": [401]
}Only session.endpoint changed; everything else was preserved from the author's defaults.
Added in v1.13.0
By default, SessionAuthStrategy mirrors the wrapped base strategy's headers (e.g., Authorization: Basic … from BasicAuthStrategy) onto every post-handshake API request alongside the session cookies. For most APIs this is harmless — the cookie-based auth is what the server checks, and the extra header is ignored.
For some stateful backends — Spring Security with 2FA, for instance — re-presenting the base credentials on every call re-triggers the auth filter and either re-prompts, returns 401, or invalidates the active session. Set dropBaseHeaders: true to strip the base strategy's headers after the handshake:
import { createCli, BasicAuthStrategy } from "@apijack/core";
const cli = createCli({
name: "myapi",
description: "My API CLI",
version: "1.0.0",
specPath: "/v3/api-docs",
auth: new BasicAuthStrategy(),
sessionAuth: {
session: { endpoint: "/session" },
cookies: { extract: ["SESSION"], applyTo: ["POST", "PUT", "DELETE"] },
dropBaseHeaders: true,
},
});Or in ~/.apijack/config.json for the standalone CLI:
{
"sessionAuth": {
"session": { "endpoint": "/session" },
"cookies": { "extract": ["SESSION"], "applyTo": ["POST", "PUT", "DELETE"] },
"dropBaseHeaders": true
}
}Behavior:
- Base headers are still sent to the
/sessionendpoint itself — the handshake works as before. - Only post-handshake API calls carry just cookies + any
headerMirrorheaders. - Drops all headers contributed by the base strategy, not just
Authorization. If a custom base strategy contributes non-auth headers you need to keep, write a customAuthStrategyinstead of using this flag. - Default is
false, which preserves backwards compatibility with existing configs.
Some servers redirect the session/login endpoint (e.g., 302 Found). SessionAuthStrategy uses redirect: 'manual' when calling the session endpoint, which means redirects are not followed. This is intentional -- following redirects can cause Set-Cookie headers from the original response to be lost.
If your session endpoint returns a 3xx status, SessionAuthStrategy will throw an error because the response is not 2xx. You may need to adjust the endpoint path or server configuration so it returns 200 directly.
If cookies.extract is an empty array, SessionAuthStrategy logs a warning at construction time:
SessionAuthStrategy: cookies.extract is empty -- no cookies will be captured
This is almost always a configuration mistake. Make sure you list every cookie name the server sets that you need for authentication.
If POST/PUT/DELETE requests fail with a 403 Forbidden or similar CSRF error, check:
-
headerMirroris configured -- Verify you have aheaderMirrorentry that maps the CSRF cookie to the expected header. -
headerMirror.applyTomatches the HTTP method -- IfapplyTois set to["POST", "PUT"]but you're making aDELETErequest, the header won't be sent on DELETE. Add the missing method. -
Cookie name matches exactly -- The
fromCookievalue must match the exact cookie name set by the server (case-sensitive). -
cookies.applyToallows the method -- The mirrored header is only sent when cookies are also being sent. Ifcookies.applyToexcludes the method, neither cookies nor mirrored headers will be applied.
Make sure refreshOn includes the status code your server returns for expired sessions (commonly 401). Without this, the client will surface the error response instead of retrying with a fresh session.
- Authentication Strategies -- Built-in auth strategies and writing custom ones
- Authentication Configuration -- Environment-level auth configuration
- Managing Environments -- Setting up and switching between environments
- Petstore Example App -- Running the example API
Essentials
Using a CLI
Authoring Routines
- Writing Routines
- Variables
- Output Capture
- Conditions & Assertions
- Loops
- Error Handling
- Sub-Routines & Meta-Commands
- Routine Testing
Building a CLI
- Building a CLI
- Authentication Strategies
- Session Auth
- Project Mode
- MCP Server Integration
- Code Generation Internals
Reference