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.
| Piece | Typical lifetime | What ending it does |
|---|---|---|
| Access token | Minutes | Forces a refresh on the next request; invisible to the user |
| Refresh token | Days to weeks | Signs the person out at the next refresh |
| Sealed cookie | Matches the refresh token | Deleting it signs out this browser only |
| Server-side session record | Until revoked or expired | Revoking 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.
1const jar = await cookies();23jar.set("px_session", sealedSession, {4 httpOnly: true, // JavaScript okuyamaz5 secure: process.env.NODE_ENV === "production", // yalniz HTTPS uzerinden6 sameSite: "lax", // CSRF yuzeyini daraltir7 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 shape | Refresh lifetime | Reasoning |
|---|---|---|
| Consumer or low-risk | 30 days, sliding | Interruption costs more than the residual risk |
| Ordinary B2B SaaS | 7 to 14 days, sliding | A working fortnight without re-authentication |
| Financial or administrative | 1 to 3 days | Shorter exposure, with step-up on sensitive actions |
| Shared or kiosk devices | Hours, non-sliding | The device outlives the person using it |
| Enterprise with a policy | Whatever the customer says | Make 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.
1import { Paycux } from "@paycux/node";2import { cookies } from "next/headers";34const paycux = new Paycux(process.env.PAYCUX_API_KEY);56export async function currentSession() {7 const jar = await cookies();8 const sealed = jar.get("px_session")?.value;9 if (!sealed) return null;1011 const session = paycux.userManagement.loadSealedSession({12 sessionData: sealed,13 cookiePassword: process.env.PAYCUX_COOKIE_PASSWORD,14 });1516 const result = await session.authenticate();1718 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 }2425 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.
- 1Revoke a single session when somebody signs out of one device from their account settings.
- 2Revoke every session for a user when their password changes, their MFA is reset, or their account is suspended by a directory event.
- 3Revoke on privilege reduction. A role change that removes access should not wait for the next refresh.
- 4Give organization admins a way to revoke sessions for their own members, and record it as an audit event.
- 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.