Access Tokens, Refresh Tokens & Graceful Retry Strategies

Share this

By now, you know the difference between authentication and authorization. You’ve explored OAuth 2.0, OpenID Connect, and even federated SSO setups across identity providers. So, what’s next? Have you ever heard or Refresh Tokens?
Note: This article belongs to Part 4.1: Token Lifecycle & Retry Logic in our Application Security series.

Here’s the thing: knowing how to set up authentication is only half the story. Once a user is logged in, the real work begins — managing tokens securely, handling expiration, and making sure no one gets kicked out unexpectedly.

👉 This blog fills a crucial (and often overlooked) gap: how to handle the lifecycle of tokens once your app is live. It’s not redundant — it’s the missing link between setting up auth and actually managing it day to day.

Let’s explore how access and refresh tokens work, why they expire, and how you can implement graceful retry strategies that keep your users authenticated and happy.

What Are Access Tokens and Refresh Tokens?

🎫 Access Token

An access token is a short-lived credential used by your app to access protected resources (like APIs). It typically contains:

  • User ID or claims
  • Scopes (what the user can access)
  • Expiry timestamp
  • Token signature (JWT or opaque)

💡 Think of it like a movie ticket — valid for a specific showtime and seat, but only for a limited time.

🔄 Refresh Token

A refresh token is a longer-lived credential used to obtain new access tokens when the old one expires. It’s never used directly to access APIs.

Refresh tokens are like a season pass — you use it to get new tickets, not enter the theater directly.

Application Security Series
Application Security Series

How Refresh Tokens Work

Let’s rewind for a moment — before we talk about refreshing, how does the refresh token even show up in the first place?

🔐 When and How Is the Refresh Token Issued?

In a typical Authorization Code flow with PKCE (the recommended flow for web and mobile apps), the identity provider issues the tokens after the user successfully logs in.

Here’s what happens:

  1. The app redirects the user to the authorization server
  2. The user authenticates
  3. The app exchanges the received authorization code for tokens

If all goes well, the authorization server responds with:

{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "def50200b9a1...",
"expires_in": 3600,
"token_type": "Bearer"
}

So, no — the refresh token doesn’t “come with” the JWT (access token). It’s issued alongside it, as a separate credential, only when the request qualifies.

When do you get a refresh token?

  • When using the Authorization Code flow (with or without PKCE)
  • If the offline_access scope is included in the request
  • If the client app is registered correctly to allow issuing refresh tokens (some IdPs require enabling it explicitly)

⚠️ For SPAs or public clients, refresh tokens may not be issued unless you’re using a backend or a trusted app flow.

🗃 Where Is the Refresh Token Stored?

Where (and how) you store the refresh token depends on the type of client:

  • Backend apps (confidential clients):
    • Store securely in the backend session or database
    • Spring Security handles this through OAuth2AuthorizedClientManager
  • SPAs or mobile apps (public clients):
    • Avoid storing in localStorage (vulnerable to XSS)
    • Prefer secure, HTTP-only cookies
    • Or better: use a backend-for-frontend (BFF) architecture to manage tokens safely on the server side

If you’re not receiving a refresh token:

  • Make sure you’re using the right grant type
  • Include offline_access in the scope
  • Double-check client settings in your identity provider

Why Tokens Expire (and That’s a Good Thing)

Token expiry is a feature, not a bug. Expiring tokens:

  • Limit the damage if a token is stolen
  • Encourage re-verification of identity periodically
  • Allow dynamic enforcement of security policies

Typical lifetimes:

  • Access token: 5–15 minutes
  • Refresh token: Hours, days, or even weeks (with rotation)

If you’ve ever been logged out of an app right in the middle of something, chances are the app didn’t handle token expiry well.

How Refresh Tokens Work

Here’s the standard flow:

  1. User logs in and receives access and refresh tokens
  2. Access token is used for all API requests
  3. When access token expires, the app:
    • Silently sends the refresh token to the token endpoint
    • Receives a new access token (and sometimes a new refresh token)
    • Continues without bothering the user

This is how modern apps achieve silent authentication — no password prompts, no session loss.

Refresh flows are supported in Authorization Code Grant with PKCE, which is ideal for web and mobile apps.

Token Expiry Handling: The Good, The Bad & The Retry

❌ Bad Strategy:

User makes an API call with an expired access token and gets a 401. App shows “Session Expired. Please log in again.”

Painful.

✅ Good Strategy:

  1. App detects token expiry or handles 401 response
  2. It uses the refresh token to silently obtain a new access token
  3. Retries the original request automatically
  4. User stays blissfully unaware anything happened

This is called a graceful retry strategy, and it’s critical for a smooth experience.

Graceful Retry Patterns in Spring Security

With Spring Security, you can use OAuth2AuthorizedClientManager to manage token lifecycles and refresh logic.

Here’s a simplified setup:

@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clients,
OAuth2AuthorizedClientRepository authClients) {

OAuth2AuthorizedClientProvider provider =
OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken()
.build();

DefaultOAuth2AuthorizedClientManager manager =
new DefaultOAuth2AuthorizedClientManager(clients, authClients);
manager.setAuthorizedClientProvider(provider);

return manager;
}

This ensures that Spring:

  • Detects when an access token expires
  • Triggers a refresh request using the stored refresh token
  • Automatically applies the new access token on retry

No manual token management. No broken UX.

Best Practices for Token Management

Keep access tokens short-lived
They’re often stored in memory or passed over networks — keep the window of risk minimal.

Use refresh token rotation
Every time a refresh token is used, issue a new one. If the old one is reused — revoke it.

Store refresh tokens securely
Never put them in localStorage or expose them to frontend JS if avoidable. Use secure, http-only cookies or a backend token handler.

Handle edge cases
What if both tokens expire? What if refresh token is revoked? Always have a fallback that leads the user gently back to login.

Conclusion

Tokens are at the heart of any secure modern authentication system. Understanding how they expire — and how to gracefully renew them — separates good user experience from frustrating one.

You’ve set up OAuth flows. Now you’ve got the tools to manage them like a pro.

This isn’t just another post in the series — it’s the glue between everything you’ve built and the way your app behaves every day.

Check out https://stackoverflow.com/questions/72390216/how-to-get-refresh-token-from-spring-authorization-server-sample Stack overflow link for more information on the current topic.

What’s Next

Up Next in our Application Security Series is:
Part 4.2 👉 Inside JWTs: Anatomy, Validation & Expiry Pitfalls

We’ll open up the black box of JSON Web Tokens — how they’re structured, signed, validated, and where most implementations go wrong.

Have a Question?

Not sure how to wire token refresh in Spring Boot? Wondering if your access tokens live too long? Confused by 401s that won’t go away?

Drop a comment we’d love to help untangle the retry spaghetti.

Share this

Leave a comment

Your email address will not be published. Required fields are marked *