Est.

Session Token Strategy and Refresh Token Rotation in SaaS APIs

Staff Writer · · 13 min read
Cover illustration for “Session Token Strategy and Refresh Token Rotation in SaaS APIs”
Authentication and User Management · August 18, 2026 · 13 min read · 2,985 words

Session tokens run every SaaS API on earth, and almost nobody thinks about them until something breaks. This piece is about the two decisions that actually matter: how long an access token should live, and how carefully you rotate the refresh token behind it. Get both right, and users stay logged in without you handing attackers a long-lived key to your entire system.

Quick definitions before we go further, because the words get thrown around loosely. Access tokens are short-lived credentials, checked on every single API call. Refresh tokens are the long-lived ones, traded quietly in the background so users don't have to log in every ten minutes. One gets checked constantly and in public. The other gets checked rarely and in private. That difference is the whole ballgame, and it's why treating them with the same security rules is like using the same lock on your front door and your bike, one of those things gets stolen a lot more than the other.

JWTs make this trickier. They're stateless by design, so if one gets stolen, there's no server-side switch to flip. It just stays valid until it expires, whether you notice the theft or not. Refresh tokens sit outside your SSO and MFA perimeter entirely, they're a separate access road that doesn't run through the same checkpoint. And here's the part users never see: the "stay logged in" feeling they love is carried almost entirely by the refresh token. The access token is the invisible plumbing. Users never think about it, which is exactly why engineers need to.

Venn diagram: Access Tokens vs. Refresh Tokens. Compares Access Tokens and Refresh Tokens; overlap: Shared Concerns.

How short an access token lifetime should actually be, and what drives that decision

Shorter access tokens limit the damage if one gets stolen. Longer ones cut down on refresh round-trips and latency. That's the whole tension, and most APIs land somewhere between five and fifteen minutes as a result, short enough that a stolen token is basically expired by the time anyone tries to use it for real damage, long enough that you're not refreshing constantly and slowing everything down.

Where you land in that range depends on what the endpoint touches. Financial data, admin panels, healthcare records: stay on the short end, maybe even under five minutes. Standard product surfaces, the dashboards and normal app usage most SaaS companies run: ten to fifteen minutes is comfortable. Low-sensitivity, read-mostly public endpoints can stretch to thirty or sixty minutes without meaningfully raising your risk.

This isn't a guess. RFC 9700, published by the IETF in January 2025, updated the OAuth 2.0 security guidance and explicitly names access token lifetime as a primary risk control for sensitive APIs. It's not a suggestion buried in a footnote, it's the baseline now.

Refresh tokens run on a separate clock entirely. Inactivity timeouts are common: thirty days is a typical threshold for B2B SaaS, and mobile apps often stretch that further since people don't open their banking app every day but still expect to skip the login screen. Layer an absolute session cap on top of that, and you get two independent clocks running at once. A session stays valid as long as the user's active and the absolute cap hasn't hit. Neither clock cares about the other.

The upshot: access token lifetime isn't one dial you set once and forget. It's a per-endpoint policy, and it needs revisiting as your product grows new surfaces with new risk profiles.

What refresh token rotation actually does — and the reuse detection logic that makes it a real security control

Diagram: Reuse Detection: How Family Revocation Works. Visualizes: Visualize the step-by-step logic of refresh token reuse detection and family revocation.

Rotation is simple in concept. Every time a refresh token gets exchanged for a new access token, the server also issues a brand new refresh token and kills the old one on the spot. No token gets reused twice, in theory.

In theory is the key phrase there, because rotation by itself doesn't stop much. If an attacker captures a rotated token before your client gets around to storing it, they can still replay it. Rotation alone just means the token changes shape periodically, it doesn't mean stolen ones are useless.

That's where reuse detection earns its keep. The server marks each refresh token as used the moment it's exchanged. If that same token shows up a second time, something's wrong, because a legitimate token should never be presented twice. The server treats that as a breach signal and revokes the entire token family: every token issued in the chain going back to the original grant, not just the one that got flagged. The legitimate user gets logged out in the process, and that's not a bug, that's the point. A forced re-login is annoying for thirty seconds. A quietly compromised account is annoying for months.

There's a grace period built in for practical reasons. Okta defaults to a thirty-second window where the previous token stays valid after rotation, so clients mid-request don't get falsely flagged as attackers just because their network was slow. The IETF's OAuth Browser-Based Apps draft has made this the floor, not a nice-to-have: authorization servers must either rotate refresh tokens on every use or use sender-constrained tokens for public clients. Salesforce is rolling out mandatory rotation in its Summer '26 release. AWS Cognito added native rotation support in April 2025. Whether your team has planned for this or not, it's arriving.

The part almost everyone gets wrong is family revocation. If you only kill the one token that got presented twice and leave its older siblings alive, you've built a security control that looks real and does nothing. It's like changing the lock on your front door but leaving the spare key under the mat.

Where tokens actually live — and why storage is where rotation policies fall apart in practice

localStorage is convenient, and that's basically the only nice thing to say about it. Any script running on the page, including a rogue npm package three dependencies deep in your build, can read it. A refresh token sitting in localStorage is a refresh token available to anyone who compromises your frontend, and no rotation policy fixes that, because rotation assumes the token is safe in transit and at rest between exchanges.

HttpOnly cookies, marked Secure and SameSite=Strict, close that door. JavaScript literally cannot touch them, which removes the main path an XSS attack would use to grab your refresh token in the first place.

The strongest setup is the Backend for Frontend pattern, usually called BFF. The browser only ever holds a session cookie. A thin server-side layer holds the actual refresh token in a server-side session store, and the refresh exchange happens server-to-server, entirely out of the browser's reach. It's more infrastructure to run, no question, but it's the difference between hiding your house key under the mat and giving it to a locksmith who checks IDs before letting anyone in.

There's a gap that storage alone can't close, and it comes back to the stateless nature of JWTs. There's no server-side lever to yank one back once it's issued. The only reliable fix is short lifetimes, so a stolen token dies of natural causes, paired with rotation for active revocation through family invalidation.

One more thing that gets skipped early on: encrypting refresh tokens at rest on your server. It sounds obvious, but plenty of early implementations store them in plaintext because rotation felt like the priority. A database breach without at-rest encryption exposes every stored token regardless of how good your rotation policy is. Storage and rotation aren't separate decisions, they're coupled. A great rotation scheme built on top of localStorage is weaker than a basic rotation scheme built on HttpOnly cookies. The foundation matters more than the fancy stuff on top.

The thundering herd problem — why concurrent requests at token expiry break naive refresh implementations

Picture a SaaS dashboard loading up. It fires off six or seven API calls at once, because that's how modern dashboards work, everything loads in parallel. The access token happens to be expired right at that moment. Every single one of those calls gets a 401 back, and every single one independently decides to refresh.

With rotation turned on, that's a problem. Multiple requests try to exchange the same refresh token at nearly the same instant. The server sees what looks like reuse, because technically it is, just not malicious reuse. Reuse detection fires, the whole token family gets revoked, and a completely legitimate user gets logged out for the crime of loading a dashboard.

The fix isn't a protocol change, it's a client-side engineering pattern: atomic refresh logic, usually built as a queue or subscriber setup. The first request that notices the token's expired grabs a lock and kicks off the refresh. Every other request that hits the same wall in that window queues up behind it instead of racing to refresh on its own. Once the new token comes back, everyone in the queue gets it and moves on. Nobody else ever tries to spend the old, now-dead token.

This is worth saying plainly: the auth server isn't broken in this scenario. It's doing exactly what it's supposed to do. The client is what determines whether rotation causes false alarms or works invisibly in the background. Teams building data-heavy dashboards or anything real-time run into this constantly, and it's exactly the kind of coordination problem that's easy to skip in a rush to ship, then painful to debug once support tickets start rolling in about random logouts.

What the Salesloft breach shows about refresh tokens that most incident post-mortems miss

Early 2025 gave us a case study nobody wanted. Attackers who compromised Salesloft didn't need malware, didn't need to phish anyone, and didn't need a zero-day. They used refresh tokens that were already sitting in Salesloft's environment, tokens connected to customer Salesforce instances that had been authorized months or years earlier and quietly forgotten.

The blast radius hit over 700 downstream organizations. Obsidian Security researchers pointed out the impact was roughly ten times larger than prior incidents where attackers went after Salesforce directly. That gap between "attacker breaks in" and "attacker breaks into everyone connected to the thing that broke in" is entirely a refresh token story.

Here's what most post-mortems skip past. Most SaaS applications don't automatically kill existing refresh tokens when a password gets reset or MFA gets re-enrolled. So the standard incident response playbook, reset the password, force MFA, rotate the API keys, does nothing to a refresh token that's already out there. It just keeps working. The most dangerous tokens are the old ones nobody's watching: integrations from a vendor relationship that ended two years ago, tokens issued to an employee who left the company, connections nobody remembers setting up.

Entro Security's 2025 State of NHIs and Secrets in Cybersecurity report found 44% of non-human identity tokens are exposed somewhere in the wild, and 91% of former employee tokens remain active after offboarding. Read that twice. Nine out of ten tokens tied to people who no longer work somewhere are still live.

The uncomfortable part of the Salesloft story is that nothing about those tokens was malicious to start with. They were legitimately issued integrations that simply outlived their purpose and were never revoked. Nobody did anything wrong at the moment of authorization. The mistake was silence afterward. That's the real lesson: revoking tokens tied to identity lifecycle events, someone leaving, a permission changing, an integration getting deprecated, matters just as much as your rotation policy during active sessions. Rotation protects you while things are moving. Lifecycle revocation protects you after things stop.

Sender-constraining tokens — when rotation is not enough and binding tokens to a specific client is the right next step

Rotation has a blind spot. If an attacker steals a refresh token and races the legitimate client to the exchange endpoint, they can win that race. Rotation assumes the rightful owner gets there first. It doesn't guarantee it.

Sender-constraining tokens close that gap by tying a token to a specific client, not just a specific chain of exchanges. Demonstration of Proof of Possession, DPoP for short, has the client generate a key pair up front. The public key gets bound to the token when it's issued, and every request afterward includes a signed proof that the client actually holds the matching private key. Steal the token without the private key, and you've stolen something useless, like grabbing someone's house key without knowing which house it opens.

Mutual TLS does something similar at the transport layer: the token gets bound to a client certificate, and it only works when presented alongside that exact certificate. RFC 9700 calls for sender-constraining on sensitive APIs specifically, and it mandates PKCE across all OAuth flows, including ones using refresh tokens.

None of this needs to be everywhere. Financial data, admin access, healthcare information, anywhere a compromised session costs real money or real harm, that's where the extra implementation weight is worth carrying. Standard product surfaces and read-mostly APIs are usually fine with rotation alone, especially if your threat model doesn't include someone capable of intercepting traffic in transit. Sender-constraining adds real complexity around client-side key management. The right call comes down to what the token unlocks, not what's technically possible to build.

How token rotation policy has to change when AI agents are the clients — not humans

Non-human identities now outnumber human ones roughly seventeen to one in the average enterprise, and that population grew 44% year over year between 2024 and 2025. AI agents are the fastest-growing slice of an already fast-growing category, and most token strategies were written before anyone was issuing credentials to something that isn't a person.

A human token carries a lot of context: role, department, access to half the app because that's what the job requires. Handing that same token to an agent running one narrow task is a mismatch. The agent doesn't need department-wide access to summarize a support ticket, and giving it that access anyway is how a small automation script ends up with the keys to everything.

Token exchange, specified in RFC 8693, fixes this by swapping a broad human token for a narrow, short-lived one scoped tightly to whatever the agent's actually doing. It's least privilege applied to delegation, and it matters more here than almost anywhere else, because agents act fast and act often, without a human pausing to sanity-check each request.

The Model Context Protocol, MCP, is turning into the standard way agents connect to tools, and it's moving fast. Over 13,000 MCP servers went up on GitHub in 2025 alone. The June 2025 spec update brought in OAuth 2.1 and adopted RFC 9728 for protected resource metadata, which lets agents discover authorization requirements on the fly instead of relying on hardcoded settings baked in at deploy time. That's the scalable way to manage rotation across a growing web of tool connections, rather than hand-configuring each one.

The hardcoded secret problem is where this gets ugly. GitGuardian's State of Secrets Sprawl 2026 report counted 28.65 million hardcoded secrets added to public GitHub in 2025, up 34% year over year, with AI service-related secrets specifically surging to 1.27 million incidents. That's the agent version of storing a refresh token in localStorage: a credential handed out once at deploy time, never rotated, quietly waiting to be found in a public repo.

CAEP, the Continuous Access Evaluation Profile, gives agents something close to the human equivalent of forced re-login. It propagates revocation events to connected services in real time, so when something looks wrong, access doesn't linger. Platforms built for agent-native workflows, where the agent itself can handle integration setup and token rotation rather than a developer hand-wiring credentials once and forgetting them, cut down that hardcoded-secret surface area considerably. Tiun's approach to MCP integrations and agent-driven workflows is built around exactly that operational reality: agents that manage their own credential lifecycle instead of inheriting a static secret and running with it indefinitely.

Applying token strategy in multi-tenant SaaS — where one misconfigured token policy can affect every customer at once

Everything above gets scarier at multi-tenant scale. In a single-tenant app, a rotation bug or a missed revocation is a contained mess, annoying, but contained. In multi-tenant SaaS, the exact same mistake doesn't stay in its lane. It touches every customer on the platform at once, because they're all running through the same token infrastructure.

The fix starts with how you organize tokens in the first place. Index by tenant and provider, using a composite key of tenant ID, provider, and user ID, so that revocation, rotation, and audit queries stay scoped to exactly the tenant they're supposed to touch and can't accidentally bleed into another customer's data. Encrypt everything at rest with field-level encryption, and enforce tenant isolation with strict role-based access control down at the data layer, not just as a check at the API gateway. The API layer is the front door. The data layer is where the actual damage happens if isolation fails.

Proactive refresh matters more here too. Waiting for a 401 and reacting is fine at small scale, but at multi-tenant scale, reacting means every tenant's dashboard is potentially hitting the thundering herd problem at slightly different times, all day, every day. Refreshing tokens ahead of expiration keeps per-tenant token state current and shrinks that failure window considerably.

The full flow, run per tenant, looks like this: detect a token approaching expiry, send the refresh request to the authorization server, atomically update that tenant's token store, and log the event for audit purposes. If the refresh fails, prompt re-authorization outright. Don't fail silently, and don't cache a stale token hoping the next attempt fixes it, because a silent failure in a multi-tenant system doesn't stay quiet for long. It just waits for the worst possible moment to surface, usually during a customer's own busiest hour, and by then it's not a bug ticket anymore, it's a support fire drill with someone's name on it.

Sources

  1. obsidiansecurity.com
  2. ssojet.com
  3. reform.app
  4. securityboulevard.com
  5. guptadeepak.com

More in Authentication and User Management