Skip to content

How-to

Session management

A session is the answer to a question your application asks on every single request: is this still the person who signed in? Getting it right is mostly about two things — where the session is stored, and how quickly you can end one.

What a session is made of

A Paycux session is a short-lived access token paired with a longer-lived refresh token, sealed together into one encrypted value you store in a cookie. The seal is what lets you keep both in a single place without exposing either to the browser.

The access token carries the claims your application reads — user, organization, role, permissions — and expires in minutes. The refresh token exchanges for a new one, and expiring it is what actually signs somebody out.

PieceTypical lifetimeWhat ending it does
Access tokenMinutesForces a refresh on the next request; invisible to the user
Refresh tokenDays to weeksSigns the person out at the next refresh
Sealed cookieMatches the refresh tokenDeleting it signs out this browser only
Server-side session recordUntil revoked or expiredRevoking it ends the session everywhere, immediately

Cookie settings that matter

Four attributes carry nearly all of the security of a cookie-based session. None of them are defaults you can rely on.

app/callback/route.ts
1const jar = await cookies();
2
3jar.set("px_session", sealedSession, {
4 httpOnly: true, // JavaScript okuyamaz
5 secure: process.env.NODE_ENV === "production", // yalniz HTTPS uzerinden
6 sameSite: "lax", // CSRF yuzeyini daraltir
7 path: "/",
8 maxAge: 60 * 60 * 24 * 14,
9});

Use sameSite lax rather than strict unless you have tested the sign-in redirect with it. Strict prevents the cookie from being sent on the navigation back from an identity provider, which produces a sign-in loop that looks like a session bug.

Choosing a lifetime

There is no correct number, only a trade-off between how often people are interrupted and how long a stolen session stays useful. What matters more than the number is having a fast revocation path, because that is what turns a long lifetime from a liability into a convenience.

Product shapeRefresh lifetimeReasoning
Consumer or low-risk30 days, slidingInterruption costs more than the residual risk
Ordinary B2B SaaS7 to 14 days, slidingA working fortnight without re-authentication
Financial or administrative1 to 3 daysShorter exposure, with step-up on sensitive actions
Shared or kiosk devicesHours, non-slidingThe device outlives the person using it
Enterprise with a policyWhatever the customer saysMake it configurable per organization

Refreshing without a flicker

Refresh on the server, on the request that needs it, and write the new sealed value back to the cookie in the same response. Doing it client-side produces a race whenever two requests refresh at once, and the loser gets signed out.

lib/session.ts
1import { Paycux } from "@paycux/node";
2import { cookies } from "next/headers";
3
4const paycux = new Paycux(process.env.PAYCUX_API_KEY);
5
6export async function currentSession() {
7 const jar = await cookies();
8 const sealed = jar.get("px_session")?.value;
9 if (!sealed) return null;
10
11 const session = paycux.userManagement.loadSealedSession({
12 sessionData: sealed,
13 cookiePassword: process.env.PAYCUX_COOKIE_PASSWORD,
14 });
15
16 const result = await session.authenticate();
17
18 if (!result.authenticated && result.reason === "access_token_expired") {
19 const refreshed = await session.refresh();
20 if (!refreshed.authenticated) return null;
21 jar.set("px_session", refreshed.sealedSession, { httpOnly: true, sameSite: "lax", path: "/" });
22 return refreshed;
23 }
24
25 return result.authenticated ? result : null;
26}

Revocation is the important half

Everything above is comfort. Revocation is safety, and it is the part that gets tested during an incident rather than during development.

  1. 1Revoke a single session when somebody signs out of one device from their account settings.
  2. 2Revoke every session for a user when their password changes, their MFA is reset, or their account is suspended by a directory event.
  3. 3Revoke on privilege reduction. A role change that removes access should not wait for the next refresh.
  4. 4Give organization admins a way to revoke sessions for their own members, and record it as an audit event.
  5. 5Rotate the session on sign-in rather than reusing whatever identifier was already in the browser, which closes off session fixation.

Test revocation with an open session in another browser, not with a fresh sign-in. A revocation path that only works on the next login is not a revocation path.

Step-up, and what a session does not prove

A valid session proves somebody authenticated at some point. It does not prove the person in front of the screen right now is the same one, and for irreversible actions that difference matters.

Require a fresh factor before changing an email address, moving money, exporting data or deleting an account. Record when the last step-up happened rather than only whether MFA was used at sign-in, so the check means something.

Where not to put a session

  • Not in local storage. Anything running in the page can read it, which turns every third-party script into a session risk.
  • Not in a URL. Query strings end up in logs, in referrer headers and in pasted links.
  • Not in a non-HttpOnly cookie. The convenience of reading it from JavaScript is not worth the exposure.
  • Not duplicated into your own database as a second source of truth, which will drift from the first.