Sessions & Auth
How the client authenticates, manages cookies, CSRF tokens, and session state
Authentication
The client supports two authentication methods, both configured at construction time
via ADTClient. src/AdtClient.ts:315
Basic auth (username + password)
const client = new ADTClient(
"https://my-sap-system:8000",
"MYUSER",
"password",
"001", // SAP client / mandant
"EN" // language
)
await client.login()
OAuth / Bearer token
Pass a BearerFetcher callback instead of a password string. It is called
lazily before any request that needs a token and the result is cached until the next
login. src/AdtHTTP.ts:339
const client = new ADTClient(
"https://my-sap-system:8000",
"MYUSER",
() => fetchTokenFromIdp(), // BearerFetcher — async () => string
"001",
"EN"
)
The SAP client number (001) and language are appended as
sap-client and sap-language query parameters on every
request. src/AdtHTTP.ts:225
Login flow
login() performs a single bootstrapping request that seeds both the
cookie jar and the CSRF token in one shot.
src/AdtHTTP.ts:214
- Sets the internal CSRF token to the sentinel value
"fetch" - Makes a GET to
/sap/bc/adt/compatibility/graphwithX-CSRF-Token: fetchand basic auth / bearer in theAuthorizationheader - SAP responds with
Set-Cookieheaders andX-CSRF-Token: <real-token> - Cookies are stored in the internal cookie jar; the real token replaces
"fetch"incommonHeaders
All subsequent requests automatically include the stored cookies as a
Cookie header and the cached CSRF token as
X-CSRF-Token — no further login calls are needed until the session
expires.
login()
was never called explicitly. Concurrent calls are deduplicated — only one login
request ever runs at a time. src/AdtHTTP.ts:215
What gets sent on every request
All four of these are assembled in AdtHTTP.request() before dispatch.
src/AdtHTTP.ts:322
| Header | Value | Purpose |
|---|---|---|
Authorization |
Basic <base64> or Bearer <token> |
Credential proof on every request |
X-CSRF-Token |
Opaque token returned at login | SAP's CSRF protection — required on all mutating requests |
X-sap-adt-sessiontype |
stateful, stateless, or "" |
Tells the server whether to maintain server-side session state |
Cookie |
All cookies from the jar, semicolon-separated | Carries the SAP session ID (SAP_SESSIONID_*) back to the server |
Cookie header is only set manually in Node.js environments
(runningInNode check). In a browser the browser handles cookies
automatically, so the client skips this step to avoid conflicts.
src/AdtHTTP.ts:324
CSRF token lifecycle
SAP ADT requires a valid X-CSRF-Token on all POST,
PUT, and DELETE requests. The client manages this
automatically. src/AdtHTTP.ts:36
| State | Token value | Meaning |
|---|---|---|
| Pre-login | "fetch" |
Sentinel — triggers a login cycle on next request |
| Logged in | Opaque string from SAP | Sent as-is on every request |
| Token rejected (403) | Reset to "fetch" |
Triggers re-login in stateless mode only |
| Session timeout (400) | Reset to "fetch" |
Treated the same as a CSRF error |
A CSRF error is identified by either a 403 with
X-CSRF-Token: Required in the response, or a 400
with status text "Session timed out".
src/AdtException.ts:166
Session types
The X-sap-adt-sessiontype header tells the SAP server whether to keep
state between requests. The client exposes this as the stateful property.
src/AdtHTTP.ts:44
| Mode | Header sent | Server behaviour | When to use |
|---|---|---|---|
session_types.stateless |
stateless |
Each request is independent; session state discarded after response | Reads, object creation, anything that doesn't need a lock |
session_types.stateful |
stateful |
Server ties all requests to the same session; locks are held between calls | Any write that requires lock → write → unlock |
session_types.keep ("") |
(empty string) | Server preserves whatever session type was previously negotiated | Mid-sequence requests where you don't want to change session type |
true if mode is explicitly
stateful, OR if mode is keep and the last negotiated
session was stateful. This lets you check whether locks will actually be held
without tracking mode changes manually.
src/AdtHTTP.ts:159
The recommended pattern when writing — save and restore the previous mode:
const prevstate = c.stateful
try {
c.stateful = session_types.stateful // → X-sap-adt-sessiontype: stateful
const handle = await c.lock(path)
await c.setObjectSource(path, source, handle.LOCK_HANDLE)
await c.unLock(path, handle.LOCK_HANDLE)
} finally {
c.stateful = prevstate // restore whatever it was before
}
dropSession vs logout
These are distinct operations and should not be used interchangeably.
| Method | What it does | When to use |
|---|---|---|
dropSession() |
Sets mode to stateless, then makes a
GET to /sap/bc/adt/compatibility/graph
— this signals the server to release the stateful session slot without
invalidating credentials. Cookies and CSRF token are kept.
src/AdtHTTP.ts:253
|
After a lock/write/unlock cycle — you want to stay logged in but release the server-side session |
logout() |
Sets mode to stateless, calls
/sap/public/bc/icf/logoff, then clears credentials, cookies,
and resets the CSRF token to "fetch".
src/AdtHTTP.ts:242
|
End of session — full teardown |
Stateless clone
Some operations (like createObject) leave the server in an inconsistent
state if called on a stateful session. The statelessClone property
provides a separate client instance using the same credentials but always in stateless
mode. src/AdtClient.ts:371
// statelessClone is lazily created and cached
await c.statelessClone.createObject(options)
// clones cannot be set to stateful — throws AdtException
c.statelessClone.stateful = session_types.stateful // ✗ throws
The clone logs in independently with its own cookie jar and CSRF token. If the main
client used a BearerFetcher, the same fetcher callback is reused so
token refresh is handled consistently.
Keep-alive
SAP systems time out idle sessions (typically after a few minutes). An optional keep-alive timer can be enabled at construction time. src/AdtHTTP.ts:210
const client = new ADTClient(url, user, pw, "001", "EN", {
keepAlive: true // fires a ping every 120 000 ms (2 minutes)
})
When active, the client sends a lightweight request every 2 minutes only if it has
not made any other request in that window (needKeepalive flag). This
prevents unnecessary traffic during active use.
src/AdtHTTP.ts:283
Auth error handling
| Error | Condition | Stateless behaviour | Stateful behaviour |
|---|---|---|---|
401 Unauthorized |
Bad or expired credentials | Auto re-login, retry request once | Throws — no retry |
403 + X-CSRF-Token: Required |
Token expired or missing | Auto re-login, retry request once | Throws — no retry |
400 Session timed out |
Stateful session slot expired on server | Auto re-login, retry request once | Throws — no retry |
The auto-retry path calls login() to refresh cookies and the CSRF token,
then replays the original request exactly once. If it fails again, the error is thrown.
src/AdtHTTP.ts:263