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.
| Lifetime | Typical range | What it controls |
|---|---|---|
| Access token | 5 to 15 minutes | The revocation window. Shorter means faster revocation and more refresh traffic. |
| Refresh token | 8 hours to 30 days | How long before somebody has to sign in again from a device they keep using. |
| Idle timeout | 30 minutes to 8 hours | How long an unused session survives. Independent of the refresh token's absolute expiry. |
| Absolute session cap | 8 hours to 90 days | The 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.
- 1Issue a new refresh token on every exchange and mark the previous one used.
- 2If a used token is presented again, revoke every token descended from the same original sign-in, not just the one presented.
- 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.
- 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.
- 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.
1import { Paycux } from "@paycux/node";23const paycux = new Paycux(process.env.PAYCUX_API_KEY);45export 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 });1011 const result = await session.authenticate();1213 if (result.authenticated) {14 return next(result);15 }1617 // Yalniz suresi dolmus oturum yenilenir; diger her hata cikis demektir.18 if (result.reason !== "no_session_cookie_provided") {19 const refreshed = await session.refresh();2021 if (refreshed.authenticated) {22 const response = await next(refreshed);23 response.cookies.set("px_session", refreshed.sealedSession);24 return response;25 }26 }2728 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.