Refreshing an expired token once

Networking Senior Kotlin 2.0+ API 24+ Reviewed By Best Latest Apps

Spot the bug

Under load, the server sees many refresh calls at once. Which line lets that happen?

  1. class TokenAuthenticator(private val store: TokenStore, private val auth: AuthApi) : Authenticator {
  2. override fun authenticate(route: Route?, response: okhttp3.Response): Request? {
  3. val fresh = auth.refreshBlocking(store.refreshToken) ?: return null
  4. store.accessToken = fresh
  5. return response.request.newBuilder().header("Authorization", "Bearer $fresh").build()
  6. }
  7. }
Hint 1 of 1

What happens when five threads reach this line together?

Choose an answer, then open Show answer below to check it.

Show answer
  • Answer: line 3 val fresh = auth.refreshBlocking(store.refreshToken) ?: return null

Every 401 refreshes on its own. Wrap this in synchronized(this), and first check whether store.accessToken already differs from the token on the failed request; if so, retry with it.

30-second answer

Use an OkHttp Authenticator, which runs when a response is 401, and guard the refresh with a lock. The first caller refreshes; the others wait, then see that the token has already changed and just retry with the new one. If the refresh itself fails, return null so the requests fail and the app signs the user out.

Practise saying it · 35 s. No recording, just a timer

Explain single-flight token refresh.

Say your answer out loud, then check which of these points you covered:

  • Authenticator on 401
  • Lock the refresh
  • Compare tokens to skip extra refreshes
  • Give up on refresh failure
  • Avoid retry loops

Deep dive

Compare the token on the failed request with the current one; if they differ, another caller already refreshed.

The Authenticator runs on OkHttp's threads, so a blocking call (or runBlocking around a suspend refresh) is acceptable there.

Stop after one retry per request (check response.priorResponse) to avoid loops.

Store tokens securely and update them atomically.

Types used above Compile-checked · Kotlin 2.4.21
interface TokenStore {
    var accessToken: String?
    val refreshToken: String
}

interface AuthApi {
    fun refreshBlocking(refreshToken: String): String?
}

Common mistakes

  • Each failed request starting its own refresh.
  • Retrying forever when the refresh token is also expired.
  • Refreshing in an interceptor before every request.

Senior take & follow-ups

Single-flight refresh is a classic senior question; mention the token comparison trick.

Follow-up questions

Think of your answer first, then open the hint.

  1. How do you avoid an infinite 401 loop?

    Show hint

    Give up if the response already has a priorResponse that was a 401.

  2. Why is blocking OK in an Authenticator?

    Show hint

    It already runs on a background OkHttp thread and must return a Request.

Compare on iOS: Single-flight token refresh The iOS page solves the same race with an actor.

Suggest a fix if something here is wrong or out of date.