Session policy
Each organisation has a SessionTimeoutPolicy that controls how long browser sessions stay signed in before being forced to re-authenticate.
Two knobs:
| Setting | Default | What it does |
|---|---|---|
| Max session age | 8 days (11 520 min) | Hard ceiling on session duration regardless of activity. After this, the user must sign in again. |
| Max time between activity | 3 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:
- Compares
now - last_loginagainst max session age — over the limit → the user is logged out. - 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.
Recommended values
| Use case | Max age | Idle |
|---|---|---|
| Low-sensitivity (internal dogfood, demo orgs) | 30 days | 7 days |
| Default | 8 days | 3 days |
| Compliance / strict | 24 hours | 4 hours |
| PII handling | 8 hours | 30 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