Core Concepts
Sessions & Cross-App SSO

Sessions & Cross-App SSO

The session cookie

Every sign-in method (password+TOTP, passkey, social) produces the same browser session:

PropertyValue
Cookie namecamel_session
Max-Age30 days
FlagsHttpOnly, Secure, SameSite=Lax, Path=/
Sliding expiryYes — every authenticated request extends it another 30 days and updates last_used_at

The cookie lives in the system browser (Custom Tab on Android, the normal browser on web). That shared cookie is the entire SSO mechanism.

How cross-app SSO works

No special code is required in client apps:

  1. User signs in via Bajeti → Camel Accounts sets camel_session in the browser
  2. Later, Simamia redirects to /oauth/authorize
  3. Server sees a valid session → skips login entirely
  4. If Simamia's consent was already granted → skips consent too
  5. User is back in Simamia with a code before the splash screen finishes
Bajeti login      ──► session cookie set
Simamia authorize ──► session found ──► consent already granted? ──► instant code
Ngamia authorize  ──► same             ──► first time: one consent tap only

Android requirement: launch the flow in Custom Tabs, never a WebView. A WebView has its own cookie jar — SSO breaks and PKCE guarantees weaken. AppAuth-Android does this correctly by default.

Managing sessions programmatically

Users own their sessions through the Cockpit; these endpoints power that UI.

List sessions

curl -s -b cookies.txt "$ISSUER/sessions"
{
  "success": true,
  "data": [
    {
      "id": "5e8a…",
      "device_label": "google",
      "ip_address": "41.86.176.12",
      "created_at": "2026-08-20T14:22:05Z",
      "last_used_at": "2026-08-26T08:15:31Z",
      "is_current": true
    },
    {
      "id": "c2d9…",
      "device_label": null,
      "ip_address": "197.157.200.44",
      "created_at": "2026-07-02T10:03:00Z",
      "last_used_at": "2026-08-25T19:48:12Z",
      "is_current": false
    }
  ]
}

device_label is the social provider name for social logins (google/github), otherwise null. Most-recently-used first.

Revoke one session

curl -s -b cookies.txt -X DELETE "$ISSUER/sessions/c2d9…"
# { "success": true, "message": "session revoked" }   (404 if not owned/not found)

Log out

curl -s -b cookies.txt -X POST "$ISSUER/auth/logout"
# revokes this session server-side AND clears the cookie

Connections ("the door")

Everything the user ever approved lives in their connections list:

curl -s -b cookies.txt "$ISSUER/connections"
{
  "success": true,
  "data": [
    {
      "client_id": "bajeti-web-x7k2p9",
      "client_name": "Bajeti Web",
      "scope": "openid profile",
      "granted_at": "2026-07-17T12:00:00Z"
    }
  ]
}

Revoking one connection is immediate and thorough: consent deleted, refresh-token family revoked, live access tokens killed. From your app's perspective the user is simply logged out on next API call.

curl -s -b cookies.txt -X DELETE "$ISSUER/connections/bajeti-web-x7k2p9"
# { "success": true, "message": "connection revoked" }

What logs users out globally

ActionEffect
Password resetRevokes all browser sessions of the user
Account deletionEverything goes, cascaded via foreign keys
Session revoke (per-session)Just that device
Connection revoke / client deleteAll tokens for that user+app pair
Refresh-token reuse detectionEntire family for that user+client pair

Your app should treat an unexpected 401 from userinfo as "session may be gone" → fall back to interactive login.