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 TokensThe 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 token | Refresh token |
|---|---|
| Used to call protected APIs | Used to obtain a new access token |
| Short lifetime | Longer lifetime |
| Sent with normal API requests | Sent only to the refresh API |
| Frequently used | Rarely used |
| Lower damage period if stolen | More 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_TOKENExample:
Android App
|
| Access Token
v
Account APIRefresh token
A refresh token is used when the access token expires.
Expired Access Token
|
v
Send Refresh Token
|
v
Receive New Access TokenRefresh 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 daysThe 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 tokenThe 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 invalidWhy is rotation required?
Suppose an attacker steals R1.
User uses R1
|
v
Server issues R2
|
v
Attacker tries R1 again
|
v
Replay detectedOWASP 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 R2The 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 R1Basic 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 ──> 401Without proper handling, every request may call the refresh API.
401 A -> Refresh R1
401 B -> Refresh R1
401 C -> Refresh R1With 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 CThis 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 screenAndroid-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 logout | Complete logout |
|---|---|
| Deletes tokens from the device | Revokes server session and deletes local tokens |
| Stolen refresh token may still work | Refresh token becomes unusable |
| Faster but weaker | Recommended 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:
| Control | Meaning |
|---|---|
MASVS-AUTH-1 | Use secure authentication and authorization protocols and follow relevant best practices |
MASVS-AUTH-2 | Perform 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 logoutAuthentication 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 -> StopAlways 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 login7. 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.