40 - Logout & Session Expiry
Session Types
OpenAlgo maintains separate but related state:
Flask app session
Browser is authenticated to the local application
Active-session row
Device/IP/login/last-seen audit for the app session
Broker auth row
Installation's current broker and encrypted auth/feed tokens
OpenAlgo API key
External/internal service authentication that resolves the broker row
Multiple app devices share one broker auth row and one server-side broker market-data feed.
Login And Heartbeat
Successful app/broker login assigns session_id and registers an active_sessions row. The database caps the user at five rows, replaces an existing same-user/same-IP row, and removes the oldest at the cap.
The React app reads /auth/session-status. For logged-in sessions, that request touches last_seen at most once per 30 seconds using a timestamp in the signed Flask session. This makes the security dashboard liveness field meaningful without writing on every poll.
Broker Token Rollover
The default daily expiry time is 03:00 IST. DISABLE_SESSION_EXPIRY=true supports 24/7 crypto deployments. Request guards apply expiry and revoke broker access according to this policy.
Broker expiry does not necessarily invalidate the local app session. When /auth/session-status finds an authenticated browser but no usable broker token, it preserves logged_in and returns broker_session_expired: true. The UI can then render broker reconnect and /auth/broker can admit the user.
Multi-Device Resume
On resume, upsert_auth() compares decrypted broker/feed tokens plus broker and revoke state. If nothing material changed, it does not publish a teardown invalidation. This prevents a second browser login from disconnecting the feed used by the first device.
If tokens materially change or are revoked, cache/feed invalidation remains required.
Explicit Logout
GET/POST /auth/logout clears the Flask session, revokes the broker auth row through upsert_auth(username, "", "", revoke=True), drops the auth/feed/symbol caches, removes every active-session row for the user through clear_user_sessions(), and emits force_logout plus a zeroed active_sessions_update. It is an all-device logout, not a single-device one.
The route is deliberately NOT CSRF-exempt in app.py. The POST form is covered by Flask-WTF; the GET form, which Flask-WTF never validates, is covered inside the view by _is_foreign_initiated(), a Sec-Fetch-Site fetch-metadata check that aborts with 403 for any cross-site (or same-site but not same-origin) caller.
Account Security Events
Password change clears all active-session rows, emits
force_logout, and clears the current cookie.Password reset also calls
clear_user_sessions(), emitsforce_logout, and pops thereset_token,reset_email,reset_method, andemail_reset_tokenkeys from the session.Active-session APIs under auth and security are read-only; there is no documented endpoint for remotely revoking one selected device.
Frontend Behavior
AuthSync restores user, broker, API key, app mode, capabilities, and active-session count. A normal unauthenticated response clears the Zustand stores. Network errors preserve existing state for the current render rather than forcing a false logout.
Key Files
blueprints/auth.py
Session status, heartbeat, login, logout, password change
database/auth_db.py
ActiveSession model, cap, last-seen, token upsert/revocation
app.py
Daily expiry request guard and CSRF exemptions
utils/session.py
Protected blueprint checks
frontend/src/components/auth/AuthSync.tsx
SPA session restoration
frontend/src/stores/sessionStore.ts
Active-session count
Last updated
Was this helpful?