Token Management

Token Management

1. What Is Token Management?

Token management means securely creating, storing, using, refreshing and deleting authentication tokens during a user session.

Login
  |
  v
Access Token + Refresh Token
  |
  v
Call Protected APIs
  |
  v
Access Token Expires
  |
  v
Refresh Token Generates New Tokens

The backend must validate the token and user permissions for protected APIs. Missing, invalid or unauthorized tokens must be rejected.

---

2. Token Classification

The two main tokens are:

Access tokenRefresh token
Used to call protected APIsUsed to obtain a new access token
Short lifetimeLonger lifetime
Sent with normal API requestsSent only to the refresh API
Frequently usedRarely used
Lower damage period if stolenMore sensitive because it maintains the session

Access token

An access token proves that the user is authenticated and allowed to access an API.

Authorization: Bearer ACCESS_TOKEN

Example:

Android App
    |
    | Access Token
    v
Account API

Refresh token

A refresh token is used when the access token expires.

Expired Access Token
        |
        v
Send Refresh Token
        |
        v
Receive New Access Token

Refresh tokens should be treated like sensitive credentials and protected against theft and replay.

---

3. Token Expiry

Both tokens normally have expiry times.

Access Token
Issued: 10:00 AM
Expires: 10:15 AM

Refresh Token
Issued: Monday
Expires: After several days

The exact duration is decided by the authentication server.

Expiry handling

API Request
    |
    v
Is access token valid?
   /        \
 Yes         No
  |           |
API call    Refresh token
continues       |
                v
          Get new token

The app may refresh:

1. Before the API call — when the locally known expiry time has passed.

2. After a 401 response — when the server rejects the token.

Server validation remains the final authority.

---

4. Refresh-Token Rotation

What is rotation?

Refresh-token rotation means that every successful refresh request returns:

* A new access token

* A new refresh token

The old refresh token is invalidated.

Refresh Token R1
       |
       | Used once
       v
Access Token A2 + Refresh Token R2

R1 becomes invalid

Why is rotation required?

Suppose an attacker steals R1.

User uses R1
     |
     v
Server issues R2
     |
     v
Attacker tries R1 again
     |
     v
Replay detected

OWASP recommends protecting refresh tokens through sender-constrained mechanisms or refresh-token rotation. When rotation is used, previously used refresh tokens should be invalidated so replay can be detected.

Rotation flow

POST /token/refresh
Refresh Token: R1
        |
        v
Server validates R1
        |
        v
Invalidate R1
        |
        v
Return A2 and R2

The application must save both new tokens atomically. It should never keep using the old refresh token after a successful refresh.

---

5. Normal Token Flow

1. User logs in
        |
        v
2. Server returns A1 + R1
        |
        v
3. App calls API using A1
        |
        v
4. A1 expires
        |
        v
5. App sends R1 to refresh API
        |
        v
6. Server returns A2 + R2
        |
        v
7. App replaces A1 and R1

Basic model

data class AuthTokens(
    val accessToken: String,
    val refreshToken: String,
    val accessTokenExpiresAt: Long
)

---

6. Simultaneous 401 Handling

The problem

A screen may send multiple API requests together.

Request A ──> 401
Request B ──> 401
Request C ──> 401

Without proper handling, every request may call the refresh API.

401 A -> Refresh R1
401 B -> Refresh R1
401 C -> Refresh R1

With refresh-token rotation, the first refresh invalidates R1. The remaining refresh requests may then fail.

Correct solution: Single refresh

Only one request should refresh the token. Other requests should wait.

Request A -> 401 -> Starts refresh
Request B -> 401 -> Waits
Request C -> 401 -> Waits
                     |
                     v
              New tokens received
                     |
         +-----------+-----------+
         v           v           v
       Retry A     Retry B     Retry C

This is called single-flight token refresh.

Simplified authenticator example

class TokenAuthenticator(
    private val tokenStore: TokenStore,
    private val refreshApi: RefreshApi
) : Authenticator {

    private val refreshLock = Any()

    override fun authenticate(
        route: Route?,
        response: Response
    ): Request? {

        if (responseCount(response) >= 2) {
            return null
        }

        synchronized(refreshLock) {
            val failedToken =
                response.request.header("Authorization")

            val currentToken =
                tokenStore.getAccessToken()

            // Another request may already have refreshed it.
            if (failedToken != "Bearer $currentToken") {
                return response.request
                    .newBuilder()
                    .header(
                        "Authorization",
                        "Bearer $currentToken"
                    )
                    .build()
            }

            val newTokens = refreshApi.refresh(
                tokenStore.getRefreshToken()
            ) ?: return null

            tokenStore.saveTokens(newTokens)

            return response.request
                .newBuilder()
                .header(
                    "Authorization",
                    "Bearer ${newTokens.accessToken}"
                )
                .build()
        }
    }
}
Use a dedicated refresh client that does not use the same authenticator; otherwise, a failed refresh call can trigger another refresh loop.

---

7. Logout

Logout should end both the local and server session.

Correct logout flow

User taps Logout
       |
       v
Call server logout/revoke API
       |
       v
Invalidate refresh session
       |
       v
Delete local tokens
       |
       v
Clear user session data
       |
       v
Open Login screen

Android-side example:

suspend fun logout() {
    try {
        authApi.logout(
            refreshToken = tokenStore.getRefreshToken()
        )
    } finally {
        tokenStore.clearTokens()
        sessionManager.clearSession()
    }
}

Proper session handling requires session tokens to be securely created, maintained and destroyed through the session lifecycle.

Local logout vs complete logout

Local logoutComplete logout
Deletes tokens from the deviceRevokes server session and deletes local tokens
Stolen refresh token may still workRefresh token becomes unusable
Faster but weakerRecommended for sensitive apps
For a banking application, logout should normally invalidate the server-side refresh session and clear local authentication material.

---

8. OWASP MASVS Authentication

The current OWASP MASVS authentication category contains two main controls:

ControlMeaning
MASVS-AUTH-1Use secure authentication and authorization protocols and follow relevant best practices
MASVS-AUTH-2Perform local authentication securely using platform best practices

Token-management checklist for MASVS authentication

✓ Server validates every access token
✓ Missing or invalid tokens are rejected
✓ Authorization is checked, not only authentication
✓ Access tokens have limited validity
✓ Refresh tokens are strongly protected
✓ Refresh-token rotation or replay protection is used
✓ Only one refresh executes during simultaneous 401 responses
✓ Refresh failure ends the session
✓ Logout revokes the server session
✓ Local tokens are deleted during logout

Authentication material such as session IDs and tokens must not be stored unprotected, and tokens should not be exposed through logs or other insecure locations.

---

9. Common Mistakes

1. Using the refresh token for every API

Normal APIs should use the access token. The refresh token should only be sent to the token endpoint.

2. Starting multiple refresh requests

Simultaneous 401 responses should share one refresh operation.

3. Not replacing the rotated refresh token

After receiving R2, the old R1 must not be used again.

4. Infinite 401 loop

API -> 401 -> Refresh -> Retry -> 401 -> Stop

Always limit authentication retries.

5. Clearing tokens only locally

For complete logout, revoke or invalidate the server-side refresh session as well.

6. Retrying requests after refresh failure

When the refresh token is expired, invalid or revoked:

Refresh failed
     |
     v
Clear session
     |
     v
Navigate to login

7. Logging tokens

Never print access tokens or refresh tokens in application logs.

---

10. Interview Questions and Answers

Q1. What is an access token?

An access token is a short-lived credential sent with protected API requests to represent the authenticated user.

Q2. What is a refresh token?

A refresh token is a longer-lived credential used to obtain a new access token after the current access token expires.

Q3. Why should access tokens be short-lived?

A short lifetime limits the period during which a stolen access token can be used.

Q4. What is refresh-token rotation?

Every refresh operation issues a new refresh token and invalidates the previous one. This helps detect stolen-token replay.

Q5. What happens when an access token expires?

The app uses the refresh token to request new tokens. It saves the new tokens and retries the failed request.

Q6. How do you handle multiple APIs returning 401 together?

Use a lock, mutex or shared refresh operation so that only one refresh request executes. Other requests wait and then retry using the new access token.

Q7. What happens if token refresh fails?

Clear the tokens, end the local session and send the user to the login screen.

Q8. What should happen during logout?

Call the backend logout or revocation API, invalidate the refresh session, delete locally stored tokens and return to the login screen.

Q9. Access token vs refresh token?

The access token calls normal APIs and has a short lifetime. The refresh token obtains new access tokens, has a longer lifetime and requires stronger protection.

Q10. What does MASVS-AUTH require?

It requires secure authentication and authorization protocols and secure implementation of local authentication according to platform practices.

---

11. Final Interview Answer

> An access token is short-lived and is used for protected APIs, while a refresh token is longer-lived and is used only to obtain new tokens. When an access token expires or receives a 401, the app refreshes it. With refresh-token rotation, every refresh returns a new refresh token and invalidates the previous one. For simultaneous 401 responses, only one refresh request should run while other requests wait. If refresh fails, the session is cleared and the user is logged out. A complete logout should revoke the server session and delete local tokens. Under OWASP MASVS-AUTH, authentication and authorization protocols must be secure, tokens must be properly validated, and local authentication must follow platform best practices.