Skip to main content

Session policy

Each organisation has a SessionTimeoutPolicy that controls how long browser sessions stay signed in before being forced to re-authenticate.

Two knobs:

SettingDefaultWhat it does
Max session age8 days (11 520 min)Hard ceiling on session duration regardless of activity. After this, the user must sign in again.
Max time between activity3 days (4 320 min)Idle timeout. If no request lands during this window, the session cookie expires.

Both measured in minutes; minimum of 1 each.

What it applies to​

The policy is enforced by middleware on session-cookie requests only — i.e. classic Django-session logins in a browser.

Requests authenticated with a JWT (the SPA's normal mode, the mobile app, API scripts with a PAT) bypass this policy entirely — the middleware early-returns when the request is JWT-authenticated. JWT lifetime is governed separately: access tokens live 24 hours, refresh tokens 30 days (tunable via the AFRI_REFRESH_TOKEN_DAYS env var). Refresh tokens are not rotated on use.

SCIM requests are also exempt.

How enforcement works​

On each session-cookie request the middleware:

  1. Compares now - last_login against max session age — over the limit → the user is logged out.
  2. Resets the session cookie's expiry to max time between activity (so the idle window slides forward with every request). If the user unchecked "keep me logged in" at sign-in, the session instead expires when the browser closes.

If the org has no policy row yet, one is created on first read with the defaults above; users without an active organization fall back to the platform-wide MAX_SESSION_AGE / MAX_TIME_BETWEEN_ACTIVITY settings.

Use caseMax ageIdle
Low-sensitivity (internal dogfood, demo orgs)30 days7 days
Default8 days3 days
Compliance / strict24 hours4 hours
PII handling8 hours30 minutes

There's no theoretical maximum — but very long sessions are unusual security-wise. Long-lived PATs (Personal Access Tokens) are the right primitive for "this thing needs API access forever"; sessions are for humans.

Why per-org​

Different orgs on the same platform may have different security postures. A research org running on the same hardware as a fintech-compliance project shouldn't be forced into the fintech org's strict timeout. So the policy lives at the org level, not the platform level.

API​

The policy is managed via the API — there is no settings page for it in the UI. The endpoint always acts on the caller's active organization:

# Read
curl "{{platformURL}}/api/session-policy/" \
-H "Authorization: Bearer YOUR_PAT"

# Update
curl -X PATCH "{{platformURL}}/api/session-policy/" \
-H "Authorization: Bearer YOUR_PAT" \
-d '{"max_session_age": 1440, "max_time_between_activity": 60}'

Note the permission model is deliberately loose right now: any authenticated member of the org can read and update the policy (the endpoint requires authentication only, not an admin role). Treat policy changes as a trust-your-members operation until role-gating lands.

Changes take effect on the next session-cookie request — the middleware reads the current policy on every request.

Effect on the mobile and desktop apps​

Both apps authenticate with JWTs, so this policy does not apply to them. What governs their sign-in lifetime is the JWT refresh-token window (30 days by default): a phone or laptop that stays offline longer than that must sign in again before syncing. Field deployments with long offline stretches raise AFRI_REFRESH_TOKEN_DAYS instead of editing code.

The desktop app's local mirror keeps working when the token expires — the work queues up locally and syncs back after the next sign-in.

What's next​

  • Data security → — per-project session controls that layer on top of the org-level policy
  • Security → — the overall auth + transport story