Skip to content

Concepts

Token lifetimes and refresh

Every session design is the same trade made twice: a short-lived credential that is checked often, and a long-lived one that can be taken away. Get the two lifetimes right and revocation is fast and sign-ins are rare. Get them wrong and you either sign people out during a meeting or leave a fired employee with access for a week.

Why there are two tokens

An access token is a signed statement that this person is who they say they are and holds these permissions. Verifying it requires no network call and no database read — that is the entire reason it exists. The cost of that speed is that it cannot be withdrawn: once issued, it is valid until it expires, because nothing consults a central authority to ask whether it still should be.

A refresh token is the opposite. It is opaque, it is stored server-side, and exchanging it for a new access token requires a call that checks whether the session is still alive. That call is the point at which access can be revoked, permissions re-read, and a change of role picked up.

So the access token lifetime is not a performance setting. It is your revocation window: the maximum time between somebody losing access and their access actually stopping. Everything else in this guide follows from that one sentence.

Choosing the numbers

The defaults below are a reasonable starting point for a business application. They are not a standard, and the right answer depends on what a compromised session can do in your product — an application that can move money should be at the shorter end of every row.

LifetimeTypical rangeWhat it controls
Access token5 to 15 minutesThe revocation window. Shorter means faster revocation and more refresh traffic.
Refresh token8 hours to 30 daysHow long before somebody has to sign in again from a device they keep using.
Idle timeout30 minutes to 8 hoursHow long an unused session survives. Independent of the refresh token's absolute expiry.
Absolute session cap8 hours to 90 daysThe hard ceiling. A session is ended at this point regardless of activity.

A customer's security team will ask for these four numbers by name during a review. Having them written down, with the reasoning, turns a long meeting into a short one.

Rotating refresh tokens

A refresh token that never changes is a long-lived password sitting in a cookie. Rotation means every exchange returns a new refresh token and invalidates the one just used, which limits the value of a stolen token to a single use.

Rotation also gives you theft detection for free. If a refresh token that has already been exchanged is presented again, either the network retried or somebody is replaying a stolen token — and since you cannot tell which, the safe response is to invalidate the whole session family and make the person sign in again.

  1. 1Issue a new refresh token on every exchange and mark the previous one used.
  2. 2If a used token is presented again, revoke every token descended from the same original sign-in, not just the one presented.
  3. 3Log the reuse with the session id, the address and the user agent. This is one of very few signals that reliably indicates a stolen credential rather than a confused client.
  4. 4Allow a short grace window — a few seconds — where the immediately previous token still works, or a client that fires two requests in parallel will sign itself out.
  5. 5Make the client serialise refreshes: one in-flight refresh, with other requests waiting on it, rather than each request refreshing independently.

Refreshing in the request path

The pattern that works is boring: verify locally on every request, refresh only when verification fails because the token expired, and treat every other verification failure as a sign-out rather than something to retry.

middleware.ts
1import { Paycux } from "@paycux/node";
2
3const paycux = new Paycux(process.env.PAYCUX_API_KEY);
4
5export async function withSession(request, next) {
6 const session = paycux.userManagement.loadSealedSession({
7 sessionData: request.cookies.get("px_session")?.value,
8 cookiePassword: process.env.PAYCUX_COOKIE_PASSWORD,
9 });
10
11 const result = await session.authenticate();
12
13 if (result.authenticated) {
14 return next(result);
15 }
16
17 // Yalniz suresi dolmus oturum yenilenir; diger her hata cikis demektir.
18 if (result.reason !== "no_session_cookie_provided") {
19 const refreshed = await session.refresh();
20
21 if (refreshed.authenticated) {
22 const response = await next(refreshed);
23 response.cookies.set("px_session", refreshed.sealedSession);
24 return response;
25 }
26 }
27
28 return Response.redirect(new URL("/sign-in", request.url));
29}

What revocation actually promises

When you revoke a session, refresh stops working immediately. The access token already in the browser keeps working until it expires, because nothing checks it against a central list — that was the trade you made when you chose stateless verification.

This is worth being precise about internally, because it is exactly the question a customer's security team asks: how long after we deactivate someone can they still act? The honest answer is up to one access token lifetime, and it is a number you chose. If the answer needs to be zero for some specific operation — a payment, an export, a permission change — check that one operation against the session store rather than shortening every token in the system to five minutes.

Deleting a user revokes their sessions in the same operation. The same bound applies: refresh stops at once, and an access token issued a minute earlier remains valid for the remainder of its lifetime.