OAuth Token Refresh Workflow: Secure Design, Timing, and Error Handling
By Chris Moen • Published 2026-02-24
A step-by-step guide to implement an OAuth token refresh workflow: grant details, proactive vs. reactive timing, storage by client type, single-flight refresh, errors, tests, and how Breyta orchestrates long-running refresh logic around your coding agents.
Quick answer: An OAuth token refresh workflow uses a long-lived refresh_token to obtain a new short-lived access_token from the authorization server without re-prompting the user. Implement it by storing tokens securely, refreshing proactively or on specific errors, ensuring only one refresh runs at a time, updating any rotated refresh token, retrying the original request once, and falling back to reauthentication on hard failures.
What is an OAuth token refresh?
In OAuth 2.0, a refresh uses a refresh_token to obtain a new access_token via the token endpoint, typically using the Refresh Token grant. This extends a session without asking the user to sign in again. Some providers also return a new refresh_token during this step (rotation), while others reuse the same token.
- Standardized grant: see the OAuth 2.0 Refresh Token grant and OAuth.com guide to refreshing.
- Issued alongside access_token for flows that support it (e.g., Authorization Code; public clients should use PKCE).
- Requires careful storage and narrow use to reduce risk.
Refresh vs. rotation
Refresh is the act of exchanging a refresh_token for a new access_token. Rotation is a server-side policy where each successful refresh also returns a new refresh_token and invalidates the previous one. This article focuses on refresh semantics and workflow behavior. For rotation-specific strategies (e.g., reused_token detection), consult your provider’s docs or a dedicated rotation workflow guide. An overview of rotation concepts is available in Auth0’s refresh token guide.
End-to-end OAuth token refresh workflow
- Authenticate with Authorization Code (with PKCE for public clients).
- Receive access_token (short-lived) and refresh_token (longer-lived, if issued).
- Store tokens securely; minimize exposure and access.
- Attach access_token to API calls (e.g., Authorization: Bearer ...).
- When the access_token is expired or invalid, perform exactly one refresh attempt.
- On success, update stored tokens (and replace refresh_token if rotation applies), then retry the original request once.
- On hard errors (e.g., invalid_grant), clear state and require reauthentication.
When to refresh the access token
- Proactive: Track the access_token expiry (exp or provider metadata). Refresh slightly before it expires to avoid failed requests.
- Reactive: Refresh only after receiving a 401 Unauthorized or a provider-specific invalid_token response.
- Account for clock skew and latency with a small time buffer.
- Use one strategy consistently to simplify reasoning and testing.
Secure storage by client type
- Backend applications: Store access_token and refresh_token server-side in a secure store with encryption at rest and strict access controls.
- Web with a backend: Keep refresh_token on the server; bind the browser session via HttpOnly, Secure, SameSite cookies. The server performs refresh.
- Single-page applications (public clients): Prefer Authorization Code with PKCE and a backend for refresh. If a refresh_token must reside in the browser, keep it in memory where possible, avoid localStorage, and minimize lifetime.
- Mobile and desktop: Use the system browser for auth (with PKCE) and store the refresh_token in protected OS storage (e.g., Keychain, Keystore).
- Never place tokens in URLs or client-readable cookies, and never log token values.
Single-flight refresh and concurrency
Multiple concurrent requests can trigger parallel refresh attempts, leading to race conditions, token clobbering, or lockouts with rotation. Implement a single-flight mechanism:
- Use a process-wide lock or promise-sharing so only one refresh runs at a time.
- Queue or pause other requests until the in-flight refresh completes.
- Apply atomic writes when updating the stored refresh_token (especially if rotation is enabled by the provider).
Error handling and retry rules
- Attempt one refresh per failure path, then retry the original API call once on success.
- Do not loop on hard errors such as invalid_grant or invalid_client; require reauthentication or fix configuration.
- For transient network failures, back off and retry the refresh once.
- If the provider signals a reused or revoked refresh_token, clear state and reauthenticate.
Testing the refresh workflow
- Force an expired access_token and verify a refresh occurs and the original request is retried once.
- Simulate rotation (if applicable) and ensure only the most recent refresh_token works.
- Trigger invalid_grant and confirm that the client clears tokens and restarts auth.
- Run concurrent requests to validate that only one refresh executes and others share its result.
- Verify that tokens are never logged and are encrypted at rest where stored.
Monitoring, revocation, and cleanup
- Log refresh outcomes and reasons without recording token values.
- Alert on spikes in invalid_grant or refresh failures to detect outages or abuse.
- Revoke tokens on logout or device loss if supported by your provider.
- Offer user-initiated revocation where provider APIs allow it.
- Remove token data promptly when accounts are deleted.
Common pitfalls to avoid
- Storing refresh tokens in localStorage in browser-based apps.
- Running multiple parallel refreshes across tabs, threads, or processes.
- Failing to replace the stored refresh_token when the provider rotates it.
- Leaking tokens via logs, query strings, or client-readable cookies.
- Skipping TLS or bypassing certificate validation.
How Breyta helps orchestrate a reliable refresh workflow
Breyta is a workflow and agent orchestration platform for coding agents. It is built for multi-step automations, long-running jobs, approval-heavy flows, and agent orchestration. In the context of OAuth token refresh, Breyta serves as the workflow layer around the coding agent or script you already use.
- Model a multi-step refresh workflow with deterministic execution, explicit waits, and approvals (e.g., pause and request approval before triggering a user reauthentication path).
- Version and release your refresh logic, then track clear run history for audits and troubleshooting.
- Orchestrate local agents or VM-backed agents over SSH to run refresh jobs where your code and secure storage live.
- Use reusable templates and an agent-first CLI to standardize refresh behavior across services and environments.
Because Breyta focuses on orchestration, you keep using your preferred OAuth client libraries and security stores. Breyta’s workflow layer coordinates when refresh runs, enforces single-flight behavior across steps, and gives you a reliable operational view of every run. For more on workflow orchestration tools for developers, see our related guide.
Learn more about Breyta and how it can support long-running, approval-heavy, and deterministic workflows around your coding agents. You can also explore CLI-first AI workflows for coding agents.
FAQ
Do I always need a refresh token for server-to-server APIs?
Not always. For some machine-to-machine scenarios, the Client Credentials grant is used to obtain a new access_token when needed, and no refresh_token is involved.
Should a SPA store a refresh token in the browser?
Prefer not to. Use a backend with HttpOnly cookies so refresh happens server-side. If a browser must hold a refresh_token, keep it in memory, minimize lifetime, and mitigate risk.
What if the provider does not support refresh token rotation?
You can still refresh using the same refresh_token until it expires or is revoked. Store it securely, watch for invalid_grant errors, and be ready to reauthenticate.
Can I refresh in parallel across tabs or threads?
Avoid parallel refresh. Implement a single-flight lock so only one refresh runs at a time, and have other requests wait for its result.
What happens if a refresh token leaks?
Treat it as a security incident: revoke tokens, clear sessions, require reauthentication, and investigate the cause. Rotate related secrets as needed.