Skip to content

Tokens as a Design Decision

Module 7 of 8 in the chapter Trust, Identity, and Integration Security, chapter 13 of the Integration Architect course. Each module stands on its own. Previous: What to Build On · Next: Secrets

The question this module answers: What should an integration’s tokens be allowed to do, for how long, and how do you take them back?

Back to the food festival. At the entrance you get a wristband: that’s the access token. You show it at every stall, and it only lasts a while. When it runs out, you’d normally go back through the entrance.

Some apps, like the burger house in module 5, also get something else: a re-entry pass. That’s the refresh token. It isn’t a wristband, and no stall will serve you for it, but it lets the app get a fresh wristband without going back through the entrance. That’s how an integration keeps working at two in the morning with nobody there to sign in.

Because the pass lasts so much longer, a stolen one is worth far more. That’s why the newest protections are all about the pass and the wristband, not the entrance.

The wristband and the re-entry pass The wristband and the re-entry pass, five numbered steps, with what each protects against beside it. 1, the wristband, the access token, shown on every call and short-lived: a stolen one stops working when it expires. 2, the re-entry pass, the refresh token, which gets a new wristband without the entrance: it lasts much longer, so a stolen one is worth far more. 3, each pass works once, refresh token rotation: reuse an old pass, and every wristband from it is cut off. 4, an unused pass expires after 30 days idle: a pass nobody uses can't be used by a thief either. 5, highlighted, only on the wrist it was given to, DPoP, where both sides support it: a copied wristband is useless on another machine. Underneath: rotation and the 30-day limit are on for new apps; DPoP is chosen per app. {"author": "Steve Simpson", "blueprint": "1.0.0", "builder": "standard 1.58.0", "built": "2026-11-03", "id": "DGM-INT-TW-14", "rights": "\u00a9 2026 Twinshark LLC. All rights reserved.", "template": "flow/sequence 0.1.0", "theme": "stevetecharc 1.4.0"} The wristband: the access token shown on every call; short-lived A stolen one stops working when it expires. The re-entry pass: the refresh token gets a new wristband without the entrance It lasts much longer, so a stolen one is worth far more. Each pass works once refresh token rotation Reuse an old pass, and every wristband from it is cut off. An unused pass expires after 30 days idle A pass nobody uses can't be used by a thief either. Only on the wrist it was given to DPoP, where both sides support it A copied wristband is useless on another machine. 1 2 3 4 5 WHAT IT PROTECTS AGAINST Rotation and the 30-day limit are on for new apps; DPoP is chosen per app. HOW TO READ IT 1 the order a step the step that matters most what comes next © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

Every protection makes a stolen wristband or pass worth less.

With refresh token rotation, every time the app uses its pass, it gets a new one, and the old one stops working. Salesforce: it “enforces single-use refresh tokens by issuing a new token with each exchange and immediately invalidating the previous one.” And if anyone presents an old pass, Salesforce assumes it was stolen: the reuse “results in the immediate revocation of the current refresh token and all associated access tokens” (Enforce One-Time-Use Refresh Tokens with Refresh Token Rotation (RTR) in Salesforce (opens in a new tab), Salesforce Help, October 21, 2026, checked 2026-11-07).

That’s the right behavior, and it has two design consequences:

  • The integration must save each new pass, every time, before it uses the wristband. Lose the new one, keep the old one, and the next refresh cuts the integration off.
  • No two systems may share one pass. The first refreshes, the second presents the old pass, and both are cut off. It’s the Saturday lockout again (module 4).

And when it does happen, Salesforce answers with invalid_grant. Its guidance: “Your client application should catch this specific error and initiate a fresh authentication flow rather than repeatedly retrying with the same invalid token.” Retrying the same pass a thousand times only fills the logs.

From November 4, 2026, in production, an idle pass expires: “refresh tokens expire if they haven’t been used for 30 days. When a refresh token expires due to inactivity, the associated access token is revoked.” For an integration that doesn’t use refresh tokens at all, the same control “caps the access token lifetime at 30 days from the last login date.” The reason, in Salesforce’s words: “Threat actors can use stolen OAuth tokens to get access to your Salesforce data. This change limits the window of opportunity to use a stolen or leaked token” (Refresh Token Idle TTL Is Limited to 30 Days (opens in a new tab), Salesforce Help, Winter ‘27, checked 2026-11-07). Sandboxes have had it since October 21.

A pass nobody uses can’t be used by a thief either. But think about the integration that only runs once a quarter: the year-end export, the quarterly price update. It wakes up on day 90, and its pass has been gone since day 30. It could easily have something going wrong, and if no one knows, it won’t work when it needs to. So for every low-frequency integration, decide now: a flow with no pass to expire (client credentials signs in fresh each run), a re-authorization step in the runbook, and an alert on invalid_grant. Chapter 14 builds the alert.

Here’s a detail that catches teams. For apps created from July 2026, Salesforce turns these on and locks them: PKCE for the web server flow, and “refresh token rotation and a 30-day limit to the time-to-live (TTL) for idle refresh tokens.” But “these controls aren’t currently enforced for apps that were created before July 2026,” and “if you migrate to an external client app, Salesforce doesn’t enforce these controls for the migrated app.” Salesforce’s advice: “we strongly recommend that you implement these controls across all of your apps” (OAuth Security Controls Are Enforced for New External Client Apps and Connected Apps (opens in a new tab), Salesforce Help, checked 2026-11-07).

So a connected app you migrated in module 6 comes across without rotation. Turning it on is a decision you make, app by app, and test, because the integration has to be ready to save each new pass.

One more limit to know. When people authorize an app, “The default is five authorizations per external client app per user.” Grant a sixth, and the access token unused the longest is revoked (OAuth Authorization Flows (opens in a new tab), Salesforce Help, checked 2026-11-07).

For a person with a laptop, a phone and a tablet, five is plenty. For an integration, it’s another reason for one identity per integration: six systems authorizing as one user will quietly push each other’s oldest pass out.

Every token you issue, you should know how to take back. Salesforce: “Revoke an OAuth token if you don’t want a client to access Salesforce data or if you don’t trust the client to discontinue access on its own.” Revoke a refresh token, and its access tokens go with it. There’s an endpoint for it, /services/oauth2/revoke, so an integration’s own “disconnect” or a security runbook can do it in one call (Revoke OAuth Tokens Programmatically (opens in a new tab), Salesforce Help, checked 2026-11-07).

When do you revoke? When a partner tells you they’ve been breached, as in module 3’s partner door. When an integration retires. When a secret may have leaked. Write the runbook before you need it: which app, which user, who’s allowed to pull the plug, and who’s told.

The newest protection ties the wristband to the wrist. With DPoP, Demonstrating Proof of Possession, tokens are bound to a key pair the client holds: “DPoP prevents stolen tokens from being replayed by a different client” (OAuth 2.0 Demonstrating Proof of Possession (DPoP) (opens in a new tab), Mobile SDK Development Guide, checked 2026-11-07). It’s set per app, and it only helps once both sides support it. So it adds to rotation and the idle limit; it doesn’t replace them.

The design question I’d ask: which of your integrations would you turn this on for first? Start with the ones whose tokens sit at a partner, the partner door from module 3.

Put it all together, and here’s the honest picture of a stolen token.

What a stolen token can and can't do What a stolen token can and can't do: two panels. What it can do, until it stops working, in amber: call every API its scopes allow; see everything its integration user can see; keep working until it expires or is revoked; look exactly like the integration's own calls. What it can't do, if you designed it that way, in blue: reach what the user's permissions don't allow; work after you revoke it; be reused once rotation has moved on; work from another machine, with DPoP. Underneath: design every token as if it will be stolen; the smallest user, the narrowest scopes, the shortest useful life. {"author": "Steve Simpson", "blueprint": "1.0.0", "builder": "standard 1.58.0", "built": "2026-11-03", "id": "DGM-INT-TW-15", "rights": "\u00a9 2026 Twinshark LLC. All rights reserved.", "template": "comparison/side-by-side 0.5.0", "theme": "stevetecharc 1.4.0"} What it can do until it stops working Call every API its scopes allow. See everything its integration user can see. Keep working until it expires or is revoked. Look exactly like the integration's own calls. What it can't do if you designed it that way Reach what the user's permissions don't allow. Work after you revoke it. Be reused once rotation has moved on. Work from another machine, with DPoP. Design every token as if it will be stolen: the smallest user, the narrowest scopes, the shortest useful life. © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

A stolen token is exactly as powerful as you designed it to be.

Every line on the right is a decision from this chapter: the integration user (module 4), the scopes, the revocation runbook, rotation, DPoP. So design every token as if it will be stolen: the smallest user, the narrowest scopes, the shortest useful life.

Test knowledge. Access tokens are short-lived; refresh tokens get new access tokens without signing in again. Refresh token rotation makes each refresh token single-use, and reusing an old one revokes it and its access tokens. From November 4, 2026 (production), refresh tokens idle for 30 days expire. New apps from July 2026 get PKCE, rotation and the 30-day limit enforced; older and migrated apps need them turned on. Five authorizations per External Client App per user by default. Revoke tokens at /services/oauth2/revoke. DPoP binds tokens to the client’s key pair.

  • For each integration: does it hold a refresh token at all, and if so, where it’s kept and how each new one is saved.
  • Rotation on, for every app, including the ones that came across from connected apps, tested before it’s switched on.
  • For every integration that runs less often than every 30 days: how it signs in again, and who’s alerted on invalid_grant.
  • The revocation runbook: which app, which user, who may pull the plug, and who’s told.
  • Which integrations get DPoP first, starting with tokens held by partners.