How it works
The cookie, the CSRF token, the sliding refresh — the four moving parts behind login(), and the two that leak into your code.
Read this once and every mode-specific page becomes shorter. Four server endpoints and one response header account for the entire authentication layer.
The pieces
| Endpoint | What it does |
|---|---|
api/v1/security.webtoken | Trades credentials, or a configured token, for a session |
api/v1/challenge | Fetches a CSRF token for an existing session, or deletes the session cookie |
api/v1/security.oauth · .saml | Returns the URL to redirect the browser to |
Response header sinequa-jwt-refresh | Present on any response: a refreshed CSRF token |
Signing in produces two things
Two consequences shape everything else:
- The JWT lives in an HTTP-only cookie. Your code cannot read it, and does not need to. This is why requests
are sent with
credentials: "include"and why a cross-origin deployment needs CORS configured with credentials. - What the library stores is the CSRF token, sent back as the
Sinequa-csrf-tokenheader. That is whatgetToken()returns — not the JWT, despite what some older names suggest.
Why the cookie matters even in bearer mode
An <iframe> showing a document preview, an <img> thumbnail, a download link — none of them can carry an
Authorization header. They authenticate with the cookie or not at all. That is why even token-based sign-in
tries to obtain a session cookie; see Bearer token.
The sliding refresh
Any response may carry sinequa-jwt-refresh. When it does, the library stores the new token and emits
tokenRefreshed. An active user therefore never sees the session expire: each request pushes the expiry
forward.
The corollary is the failure you will see: a session that expires while the user was idle, surfacing as a
401 on their next action. Recovering from that is Handling a 401.
The two probes, and why they are different
This distinction explains most of the mode-specific behaviour, and it is not obvious:
Prop
Type
Ambient SSO only authenticates on the second one. A sign-in that used the first and concluded "not authenticated" is how a token-less SSO session used to become unrecoverable.
What login() does
One function, three branches, in this order:
Note what login() returns in each case, because it is not uniform:
| Situation | Result |
|---|---|
| Session already present | true |
| Credentials accepted | true |
| Credentials refused | throws UnauthorizedError |
credentials mode, no arguments | false — render the form |
oauth/saml, no session | navigates away; false if it did not |
sso, handshake failed | false, and the mode is left alone |
bearer, token missing or refused | throws — there is no fallback screen for it |
Where the session is kept
| What | Where | Why it matters |
|---|---|---|
| The JWT | An HTTP-only cookie | Invisible to your code; carries <iframe> and <img> requests |
| The CSRF token | sessionStorage, under the client's storagePrefix | Cleared by closing the tab |
| "Authenticated, no token" | sessionStorage | Token-less ambient SSO has no token to check, so a flag stands in |
| "Last sign-in was a form" | localStorage | Shared across tabs of an origin; used to resolve an ambiguous mode |
All keys are prefixed by the client's storagePrefix, which is what lets two clients on one page keep separate
sessions. See Multiple backends.
Storage is injectable, and that is not just for tests
auth.storage accepts your own implementation, and the library falls back to an in-memory store when
window is absent. That is what makes it work in Node, in a worker, and in SSR — and what makes tests
deterministic without mocking modules.