Skip to main content

Authentication

The IdentSphere SDK accepts credentials in two forms. Both are equally first-class.

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...
CookieContentsRequired for
identsphere_atSigned JWT access tokenEvery authenticated request
identsphere_rtOpaque refresh tokenPOST /v1/auth/refresh, POST /v1/auth/logout
identsphere_csrfCSRF double-submit tokenEvery state-changing request from a browser

CSRF protection

State-changing methods (POST, PATCH, DELETE) sent with cookie auth require the CSRF double-submit pattern:

  1. Read the identsphere_csrf cookie (it is intentionally NOT HttpOnly for exactly this reason).
  2. Send its value back in an X-CSRF-Token header 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_token from /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:

  1. Capture the mfa_token (lifetime: 5 minutes, single-use).
  2. Prompt the user for a TOTP code OR a recovery code.
  3. POST /v1/auth/mfa/challenge with { mfa_token, code } or { mfa_token, recovery_code }.
  4. 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/register
  • POST /v1/auth/login
  • POST /v1/auth/refresh
  • POST /v1/auth/logout (degrades gracefully without auth — still clears cookies)
  • POST /v1/auth/email-otp/request
  • POST /v1/auth/email-otp/verify
  • POST /v1/auth/email/verify
  • POST /v1/auth/password/forgot
  • POST /v1/auth/password/reset
  • POST /v1/auth/passkey/login/begin
  • POST /v1/auth/passkey/login/complete
  • POST /v1/auth/mfa/challenge
  • GET /v1/auth/oauth/:provider/start
  • GET /v1/auth/oauth/:provider/callback
  • GET /v1/auth/invitations/preview
  • POST /v1/auth/invitations/accept (auth optional)