Authentication
The IdentSphere SDK accepts credentials in two forms. Both are equally first-class.
Cookie auth (browser apps)
Issued automatically by every login endpoint. The browser sends them back on every same-origin request; you don't have to touch them in JavaScript.
Cookie: identsphere_at=eyJ...; identsphere_rt=def...; identsphere_csrf=abc...
| Cookie | Contents | Required for |
|---|---|---|
identsphere_at | Signed JWT access token | Every authenticated request |
identsphere_rt | Opaque refresh token | POST /v1/auth/refresh, POST /v1/auth/logout |
identsphere_csrf | CSRF double-submit token | Every state-changing request from a browser |
CSRF protection
State-changing methods (POST, PATCH, DELETE) sent with cookie auth
require the CSRF double-submit pattern:
- Read the
identsphere_csrfcookie (it is intentionally NOTHttpOnlyfor exactly this reason). - Send its value back in an
X-CSRF-Tokenheader on every state-changing request.
const csrf = document.cookie
.split('; ')
.find(c => c.startsWith('identsphere_csrf='))
?.split('=')[1];
fetch('https://auth.example.com/v1/users/me/password', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrf!,
},
body: JSON.stringify({ current_password: 'old', new_password: 'newpw_at_least_12_chars' }),
});
::: warning Why double-submit, not synchronizer pattern? The double-submit pattern is stateless on the server side and matches the SDK's "no per-process state" design. The CSRF cookie is bound to the same domain as the API, so a cross-origin attacker can't read it; the attacker can't construct a request that has a matching cookie value AND header. :::
The @identsphere/react client handles this for you automatically.
Bearer auth (services, mobile, CLI)
Pass the access token in the Authorization header:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIi...
Bearer auth bypasses the CSRF requirement — there's no cookie to forge.
Use Bearer auth when:
- You're a mobile app that just received
access_tokenfrom/v1/auth/login - You're a server-side worker holding a long-lived token
- You're hitting the API from a different origin without CORS credential support
Don't mix: a single request shouldn't carry BOTH a cookie and an
Authorization header. The middleware prefers the header.
Refresh flow
Access tokens expire fast (default 15 min). When they do, you don't have
to log the user back in — call POST /v1/auth/refresh:
┌─────────┐ ┌─────────────────┐
│ Client │ │ IdentSphere backend │
└─────────┘ └─────────────────┘
│ GET /api/me │
│ Cookie: identsphere_at=... │
│ ─────────────────────────► │
│ │
│ 401 Unauthorized │
│ ◄───────────────────────── │
│ │
│ POST /v1/auth/refresh │
│ Cookie: identsphere_rt=... │
│ ─────────────────────────► │
│ │
│ 200 OK │
│ Set-Cookie: identsphere_at=... │
│ Set-Cookie: identsphere_rt=... │ ◄── rotated
│ Set-Cookie: identsphere_csrf=... │
│ ◄───────────────────────── │
│ │
│ GET /api/me │ ◄── retry
│ Cookie: identsphere_at=NEW │
│ ─────────────────────────► │
│ │
│ 200 OK │
│ ◄───────────────────────── │
Refresh tokens rotate on every use. The old token becomes invalid immediately — replaying a previous refresh token is a family-detection signal that revokes the entire session family.
@identsphere/react wraps this flow in an Axios interceptor; non-React clients
should run a single retry on 401 and then surface the error.
MFA-required responses
Login may return 200 OK with status: "mfa_required" instead of a full
session. The client should:
- Capture the
mfa_token(lifetime: 5 minutes, single-use). - Prompt the user for a TOTP code OR a recovery code.
POST /v1/auth/mfa/challengewith{ mfa_token, code }or{ mfa_token, recovery_code }.- On success, the response is a normal
status: "success"body — the user is now signed in.
See POST /v1/auth/mfa/challenge for the full shape.
No auth required
A small set of endpoints accept anonymous traffic:
POST /v1/auth/registerPOST /v1/auth/loginPOST /v1/auth/refreshPOST /v1/auth/logout(degrades gracefully without auth — still clears cookies)POST /v1/auth/email-otp/requestPOST /v1/auth/email-otp/verifyPOST /v1/auth/email/verifyPOST /v1/auth/password/forgotPOST /v1/auth/password/resetPOST /v1/auth/passkey/login/beginPOST /v1/auth/passkey/login/completePOST /v1/auth/mfa/challengeGET /v1/auth/oauth/:provider/startGET /v1/auth/oauth/:provider/callbackGET /v1/auth/invitations/previewPOST /v1/auth/invitations/accept(auth optional)