Session Policy & Logout
Sessions in Productify live in two places the Manager never sees: the proxy (caddy-security's authentication portal) and Pocket ID (the identity provider). This page documents the standardized logout contract every app uses, the session lifetime/idle policy per environment, and what Productify deliberately delegates to Pocket ID rather than rebuilding.
Design principle
Pocket ID is the identity source of truth: no passwords, no avatar uploads, session management delegated. Productify adds orchestration (a uniform logout URL, a documented lifetime) — never a parallel identity provider.
The logout contract
Every Productify app's "Sign out" button works identically, because the proxy injects a single logout URL into the page:
// window.__PRODUCTIFY__ (injected by the proxy's html_inject middleware)
{
// ...user, tenant, application...
"logout_url": "/_tenant/logout"
}SPAs read window.__PRODUCTIFY__.logout_url and navigate the browser to it on sign-out. They never hardcode a portal path — the proxy owns it, so the contract stays stable across apps and deployments.
One click clears two sessions
A logout must tear down both sessions that a logged-in user holds:
- the caddy-security portal session (the proxy-issued OIDC session cookie), and
- the proxy-owned tenant-selection cookie (
pfy_tenant_<app_id>), a signed cookie recording which tenant the user is acting as.
The portal logout knows nothing about the proxy-managed tenant cookie, so a single portal-logout call would leave a stale tenant selection behind. The contract solves this by routing logout through the proxy's tenant selector:
logout_urldefaults to/_tenant/logout— the tenant selector's logout route. It clears the tenant cookie (a deletion cookie,MaxAge < 0) and then 303-redirects to the caddy-security portal logout (default/auth/logout), which invalidates the portal session. One click, both sessions gone.Single-tenant apps with no tenant selector should override the injected URL to point straight at the portal logout:
caddyfileproductify_html_inject {vars.app_id} { logout_url /auth/logout }If the portal logout is mounted somewhere other than
/auth/logout(e.g./oauth2/logout), the selector's redirect target is configurable:caddyfileproductify_tenant_selector {vars.app_id} { logout_redirect /oauth2/logout }
No authentication is required to clear one's own cookie; the redirect drives the portal's own session teardown. The next request through the proxy carrying the old portal cookie is rejected — the session is invalidated immediately, ahead of its natural expiry.
Session lifetime & idle policy
The portal session lifetime is set in the proxy's caddy-security config (Caddyfile global security block):
authentication portal pocketportal {
crypto default token lifetime 3600
# ...
}token lifetime 3600— the portal session token is valid for 3600 seconds (1 hour) after issue.- Absolute, not idle. There is no separate idle timeout: after one hour the user is re-challenged through Pocket ID regardless of activity. Activity does not extend the session.
- Per-environment tuning. Set this value per deployment — shorter for higher-sensitivity environments, longer where re-authentication friction outweighs the risk. It is the single knob for the proxy-portal session.
- Pocket ID is independent. Pocket ID enforces its own upstream session/idle policy;
token lifetimebounds only the proxy-portal session, not the Pocket ID session. A user may still hold a valid Pocket ID session after the portal token expires, in which case re-authentication is a silent redirect rather than a fresh login. - Logout wins over lifetime. The logout contract invalidates the portal session immediately, ahead of the lifetime.
See the Configuration Reference for where this sits among the other proxy settings.
Pocket ID session management
Finding: session listing/revocation is delegated to Pocket ID, not rebuilt in Productify.
The plan asked whether Pocket ID's API lets Productify list or revoke a user's sessions — so a "Manage sessions" surface could be offered in the Manager profile page rather than reimplemented.
Inspecting the Manager's Pocket ID client (manager/internal/authprovider/) shows it is read-only and user-scoped only:
usersearch.goissues a singleGET /api/users?search=…with theX-API-Keyheader — user lookup, nothing else.userupsert.gocreates a localent.Userrow (materializing an already-authenticated principal); it does not touch Pocket ID.
There is no session-listing or session-revocation call in the codebase, and no verified Pocket ID management-API endpoint for it. Rather than build against an unverified/absent capability, Productify makes the delegation explicit:
- Session revocation on this platform = logout (the contract above) plus the portal
token lifetimebound. Both are proxy-side and immediate/bounded. - Cross-device "sign out everywhere" and per-session listing are owned by Pocket ID. Users manage those in Pocket ID's own account UI. Productify does not mirror or proxy that surface.
- If a future Pocket ID release exposes a verified session-management API, a "Manage sessions" link in the Manager profile page can surface it — until then it stays delegated, not rebuilt.
This keeps Pocket ID the single source of truth for identity and session lifecycle, consistent with the rest of the access model.