Blog

Engineering

How we handle OAuth token refresh

OAuth access tokens expire. When they do, users get silently disconnected from your product — often without realising it. Here is how SocialRouter manages every token lifecycle edge case so your users stay connected.

9 min read

The problem with token expiry

Here is a scenario that happens constantly in products with social integrations: a user connects their LinkedIn account in January. In April, they try to publish a post and it silently fails. They do not get an error — or they get a vague one. They think your product is broken. They churn.

The actual cause: the OAuth access token expired, the refresh attempt failed for one of a dozen reasons, and nobody told the user they needed to reconnect.

Token management is one of those infrastructure problems that sounds trivial until you implement it — at which point you discover it has a long tail of edge cases, each of which can disconnect a user with no clear warning.

How OAuth tokens actually work

When a user authorises your app to act on their behalf, the platform gives you two tokens:

  • Access token — used to make API calls. Short-lived (typically 1–2 hours for most platforms, longer for others).
  • Refresh token — used to get a new access token when the old one expires. Longer-lived, but not permanent.

The expected flow: when your access token expires, you silently use the refresh token to get a new one. The user never knows this is happening. Their integration stays alive.

In practice, this breaks constantly.

Per-platform token behaviour

Every platform implements token lifetimes differently, which means there is no single implementation that works for all of them.

  • LinkedIn — access tokens last 60 days. Refresh tokens last 365 days. You can exchange a refresh token for a new access + refresh pair. Once the refresh token expires, the user must reconnect — there is no way to silently re-authenticate.
  • Meta (Facebook/Instagram) — user access tokens generated from the standard OAuth flow expire after 1–2 hours. However, you can exchange a short-lived token for a long-lived token (60 days) in a separate step. Page access tokens derived from a long-lived user token do not expire — unless the user changes their password or revokes access.
  • Twitter/X — OAuth 2.0 tokens expire after 2 hours. Refresh tokens have no documented expiry, but can be invalidated by the user revoking access or by Twitter's internal systems. The older OAuth 1.0a tokens were non-expiring.
  • TikTok — access tokens expire after 24 hours. Refresh tokens expire after 365 days. The refresh endpoint requires a specific grant type and will return an error if called more than once within a certain window.
  • Pinterest, Threads, Bluesky — each has its own lifetime and refresh model. None are the same.

Managing this correctly means writing and maintaining a different refresh strategy per platform.

What goes wrong in practice

Even if you implement basic refresh logic correctly, there is a long list of failure modes you need to handle:

  • User changes their password. On most platforms, changing a password immediately invalidates all existing tokens. Your refresh token is now dead and the user does not know they need to reconnect.
  • User revokes app access. Users can revoke your app's access from the platform's settings at any time. Your next API call returns a 401. You need to detect this and prompt the user to reconnect — without alarming them.
  • Platform revokes access. Platforms can revoke app access without notice if they detect unusual activity, if your app violates their terms, or during platform-wide security events. This is different from a user revocation — it affects all users of your app.
  • Refresh token race conditions. In distributed systems, two processes can attempt to refresh the same token simultaneously. One succeeds; the other receives the old (now-invalid) refresh token back. Without proper locking, this invalidates the connection.
  • Refresh token rotation. Some platforms issue a new refresh token every time you use the old one (Twitter does this). If you do not store the new refresh token correctly, the next refresh fails.
  • Scope changes. If the user originally granted limited scopes and you later need expanded permissions, you cannot get them without re-prompting the user for authorisation — even if their tokens are technically valid.
  • Clock skew. Token expiry is time-based. If your server clock drifts, you may attempt to use an already-expired token, or skip refreshing one that should have been refreshed.

How SocialRouter handles it

SocialRouter manages every OAuth connection through a token lifecycle system that handles all of this transparently.

Proactive refresh. We do not wait for an access token to expire before refreshing it. We refresh proactively, before the expiry window, so that requests never hit an expired token. This eliminates the most common failure mode.

Distributed locking on refresh. Our refresh logic uses distributed locks to prevent concurrent refresh attempts for the same token. If two processes need a refreshed token simultaneously, one waits for the other to complete and then reads the freshly stored token — it does not attempt a second refresh.

Rotation-aware storage. For platforms that rotate refresh tokens on each use (Twitter, TikTok), we always persist the new refresh token immediately after a successful exchange — before we return the new access token to the caller. If storage fails, we surface the error rather than losing the token silently.

Connection health tracking. Every connection in SocialRouter has a health status. Failed refresh attempts update that status. When a connection becomes unhealthy — because the refresh token expired, the user revoked access, or any other permanent failure — we surface that to you via a webhook and in the API response, so you can prompt the user to reconnect.

Disconnection notifications. When a connection transitions from healthy to disconnected, SocialRouter fires a webhook to your endpoint with the connection ID, the user ID, and the reason. You can use this to send an in-app prompt or email asking the user to reconnect.

Edge cases we handle for you

Beyond the standard refresh flow, here are specific edge cases that our system handles:

  • Meta long-lived token exchange. When a user authenticates with Facebook or Instagram, we automatically exchange the short-lived token for a long-lived one before storing it — so you start with 60 days instead of 2 hours.
  • LinkedIn 60-day warning. LinkedIn access tokens expire after 60 days and require user interaction to renew (no silent refresh is possible beyond 365 days). We track expiry approaching thresholds and notify you before users get disconnected, so you have time to prompt them to reconnect proactively.
  • Platform-initiated revocations. We verify connection health before each post attempt. If an access token returns a 401 that cannot be resolved by refreshing, we immediately update the connection status rather than retrying indefinitely.
  • Retry isolation. A failing token for one user does not affect retry logic for other users. Our retry queues are per-connection.

What you would need to build

If you were building this in-house, here is what the complete implementation requires:

  • Per-platform refresh strategy (different TTLs, rotation behaviour, endpoint parameters)
  • Secure encrypted token storage
  • Proactive refresh job with scheduling per platform
  • Distributed lock mechanism (Redis or equivalent) for concurrent refresh prevention
  • Connection health state machine with transition logic
  • Webhook/notification system for disconnection events
  • Platform-specific error code mapping (401 vs. token-expired vs. scope-insufficient are different conditions)
  • Test coverage for each failure mode across each platform
  • Ongoing maintenance when platforms change their token policies

This is a few weeks of engineering work, plus ongoing maintenance, plus the operational burden of monitoring it. It is not a hard problem — but it is a wide one.

SocialRouter is already running this infrastructure. If you want to skip building it, you can be posting to social platforms in under an hour. See the API documentation to get started, or reach out if you want to talk through your specific use case.

Share:
All posts