
Authentication in a mobile application is often described as a simple sequence: the user logs in, receives a token, and sends that token with subsequent API requests.
That explanation is technically correct but architecturally incomplete.
A production mobile session spans several independently changing systems:
- A mobile application that may remain installed for years
- An access credential with a deliberately bounded lifetime
- A longer-lived authorization grant, when refresh capability is issued
- A device or app-instance identity
- Server-side session state
- Account, role, password, and risk changes
- Multiple API services with different consistency characteristics
- Logout and revocation events that must propagate across the platform
The central architectural mistake is treating the token as the session.
A token is only a credential representing part of a session’s authority. For systems requiring device management, replay detection, rapid revocation, or risk-based controls, the broader session should be represented by server-managed security state with its own lifecycle, device association, risk state, revocation state, and audit history.
Start With a Server-Side Session Model
After successful authentication, the backend should create a session record rather than merely issue a pair of tokens.
A practical session model might contain:
session_id
user_id
device_key_id
refresh_token_family_id
current_refresh_token_verifier
previous_refresh_token_verifier
refresh_generation
status
created_at
last_seen_at
idle_expires_at
absolute_expires_at
risk_level
auth_strength
revoked_at
revocation_reason
The mobile application receives:
- A short-lived access token
- A longer-lived refresh token
- A session identifier, directly or indirectly
- The server’s current session-policy metadata
The access token is used with resource APIs. The refresh token is sent only to the authorization or session service. It should not be attached to normal business requests.
The backend retains enough state to decide whether the session remains valid. Resource services receive that decision only when they consult the relevant session, authorization, introspection, or revocation mechanism; local signature validation alone cannot observe a later server-side revocation.
This hybrid model can preserve much of the scalability benefit of locally validated access tokens while retaining control over future refresh, sensitive-operation checks, device removal, and risk escalation. Revocation speed still depends on which endpoints consult current state.
Access Tokens and Refresh Tokens Solve Different Problems
An access token helps a resource service answer a narrow question:
Is this credential acceptable for this resource and operation under the service’s current policy?
It should contain only the claims required by resource services, such as:
- Subject or user identifier
- Session identifier
- Issuer and audience
- Granted scopes or coarse permissions
- Issued-at and expiry timestamps
- Token identifier
- Authentication strength where relevant
- Confirmation of a bound public key, if proof-of-possession is used
A refresh token answers a different question:
May this client continue an existing authenticated session and obtain another access token?
Because refresh tokens can preserve authorization beyond the access token’s lifetime, their theft is usually more consequential. They should be protected as credentials, limited to the authorized client and scope, revocable, and either sender-constrained or rotated for public clients. They are used only with the authorization service.
Making both tokens long-lived removes this separation. Making both extremely short-lived creates excessive authentication friction and availability dependence. Their different lifetimes are deliberate containment boundaries.
Exact durations depend on the risk model. A payments application might use an access token measured in minutes, an idle session measured in hours or days, and a separately enforced absolute lifetime. A lower-risk application might tolerate longer windows.
The important decision is not whether an access token lives for five or fifteen minutes. It is whether the platform can explain what happens after theft, logout, password reset, role removal, device loss, or a failed risk check.
Authentication and Session Creation
A typical login flow works as follows:
- The user authenticates using a password, OTP, passkey, federated identity, or another approved method. Redirect-based authorization flows for a public mobile client should use an authorization code flow with a transaction-specific proof challenge.
- The mobile client generates or retrieves an app-instance key pair from platform-protected storage.
- Where supported and justified, the client submits evidence about the application and key environment.
- The backend validates the authentication result and evaluates initial risk.
- The session service creates a session and refresh-token family.
- The authorization service issues an access token and refresh token associated with that session.
- The device stores the refresh credential and private key in protected storage.
- Ordinary APIs receive only the access token and, when required, proof that the client possesses the bound private key.
Mobile operating systems provide protected keystores in which key material can remain non-exportable through the normal API. Hardware-backed or hardware-isolated protection depends on the device, key type, and configuration and should be verified when policy requires it. Secret storage and user-authentication access controls also vary by platform version and device capability.
The storage API is only one control. Malware running with sufficient privilege, application instrumentation, screen control, or abuse of a legitimately unlocked device can still initiate authorized operations. Secure storage reduces extraction risk; it does not make the client trusted.
Device Binding Should Mean Key Binding
Teams frequently say that a token is “bound to the device” when it contains an IMEI, advertising identifier, installation ID, or device-model string.
That is usually correlation, not cryptographic binding.
A stronger design associates the session or token with a public key generated by the application. The backend stores the public key or its thumbprint, while the private key remains behind platform key controls where supported. For protected requests, a standardized or deployment-specific proof can cover values such as:
- HTTP method
- Request URI
- Proof creation time
- Unique proof identifier
- Access-token hash
- A server-provided nonce, when required
A request-body hash can be added by a deployment-specific signing profile for selected financial operations, but it is not covered by the common application-level proof format unless that profile explicitly defines and verifies it.
The server verifies the signature, key binding, request binding, freshness, and the deployment’s replay controls. When that validation is consistently enforced, a stolen token by itself is insufficient at those endpoints unless the attacker can also use the corresponding private key or replay an accepted proof within the remaining replay window.
App and device-integrity services can strengthen enrollment by providing evidence that a key or request originated from an expected application environment. These signals should normally feed policy rather than act as permanent identity.
Integrity verdicts can be unavailable, delayed, unsupported, or produced by a real device being used as part of an abuse network. Treating every failed or missing signal as conclusive fraud creates avoidable lockouts. Treating a successful signal as proof that the user is trustworthy creates a different security gap.
Device binding also requires a recovery policy. Reinstallation, device migration, restored data, key invalidation, biometric-enrollment changes under some access-control configurations, or operating-system behaviour can make the original key unavailable. The system must distinguish an authenticated recovery flow from silent rebinding.
Refresh-Token Rotation Is a State Transition
For a rotation-based design, every successful refresh exchange returns:
- A new access token
- A new refresh token
- A new refresh-token generation
The refresh token that was consumed becomes invalid, while the server retains enough relationship information to recognize later reuse.
This enables reuse detection. If an invalidated refresh token appears again, the backend has evidence of replay, but rotation alone cannot determine whether the caller is an attacker, the legitimate client, a delayed retry, or a concurrent process.
The difficult part is concurrency.
Mobile applications can trigger simultaneous refreshes when several API calls receive an expiry response together. Background work and foreground activity may also race. If the client has no refresh coordinator, legitimate parallel requests can resemble token theft.
The client should therefore serialize refresh operations within its controllable execution context. One request performs the refresh while other failed requests wait for its result. On success, those requests retry using the newly issued access token and a new request proof where proof-of-possession is enabled.
The server must still rotate atomically. Client serialization cannot protect against duplicated network requests, retries through gateways, multiple processes, or malicious replay.
The following pseudocode illustrates the important server-side property:
const outcome = await sessions.rotateRefreshTokenAtomically({
sessionId,
presentedVerifier: deriveVerifier(presentedRefreshToken),
nextVerifier: deriveVerifier(newRefreshToken),
expectedGeneration: tokenGeneration
});
if (outcome === "ROTATED") {
return issueTokenResponse();
}
if (outcome === "STALE_TOKEN" && replayPolicy.confirmsReplay(requestContext)) {
await sessions.revokeFamily(sessionId, "CONFIRMED_REFRESH_REUSE");
}
throw unauthorized("SESSION_REAUTHENTICATION_REQUIRED");
The transition must use a compare-and-swap or equivalent transactional condition so that only the currently accepted generation can advance the family.
If two callers present the same token, only one transition should succeed. The second request must not silently advance the generation again. The backend should classify the failure and apply a documented policy: reject and require recovery, revoke the active family when replay is established, or use a narrowly bounded duplicate-request mechanism.
Any duplicate-request allowance improves tolerance for response loss but enlarges the replay window. If used, it should be brief, bound to the same sender-constrained key where available, and designed so that it does not mint divergent refresh-token branches.
Opaque refresh tokens should normally be stored server-side using a one-way verifier or equivalent protected representation rather than in directly reusable plaintext. Tokens also need sufficient entropy so their stored verifier is not practically guessable.
JWT Validation Is More Than Signature Verification
A signed JWT can be cryptographically valid and still be unacceptable for a particular API.
Each resource service should validate every field required by its token profile and local policy, including:
- An allowed signing algorithm selected by server policy
- The signature using a trusted key
- Issuer
- Audience
- Expiry and not-before time
- Explicit token type where the profile uses it
- Required scopes or authorization details
- Session or token status where policy requires it
- Key-binding confirmation where enabled
The accepted algorithm must come from server configuration, not merely from the token header. APIs should not accept an identity token where an access token is expected, nor should a token issued for one service automatically work against another.
Authorization data also becomes stale. If a user’s role is removed while a self-contained access token remains valid, the old claim may continue granting access until expiry unless the resource service checks fresher authorization state.
For coarse, low-risk permissions, that short delay may be acceptable. High-risk operations should check current policy, an authorization version, or centralized session state. Another option is to issue narrowly scoped tokens for sensitive operations after step-up authentication.
Self-contained tokens shift work from the authorization server to resource services, but they also shift responsibility for validation, key rotation, revocation latency, and stale authorization.
Logout Is a Distributed Security Event
Deleting tokens from the mobile device is necessary, but it is not server-side logout.
A complete logout flow should attempt to:
- Request revocation of the current session or refresh-token family.
- Mark the targeted server-side state revoked when the request is accepted.
- Prevent future refreshes.
- Remove or invalidate local access and refresh credentials.
- Remove or deactivate locally stored session-key references as policy requires.
- Clear sensitive cached data according to the application’s retention policy.
- detach account-specific push or messaging registrations where appropriate.
- Record an auditable reason for termination without logging credentials.
The client must define behavior for offline or failed revocation requests. Local sign-out can still remove local access, but the server-side session may remain active until a later authenticated revocation, expiry, administrative action, or another recovery mechanism handles it.
Previously issued self-contained access tokens may remain cryptographically valid until they expire. There are several ways to reduce or govern this window:
- Keep access tokens short-lived.
- Check session status for sensitive endpoints.
- Use a distributed revocation cache.
- Use opaque tokens with centralized validation.
- Issue an emergency account-level revocation version.
These options trade latency, availability, implementation complexity, and revocation speed.
“Logout from this device” should target one session. “Logout from all devices” should revoke every active session or advance an account-level security version. Enforcement against already issued access tokens still depends on expiry or on resource services consulting current revocation state.
A password change, account recovery, confirmed compromise, administrator suspension, or major role change may require broader revocation than an ordinary user-initiated logout.
Risk-Based Expiry Is More Than Shortening a TTL
Risk-based session management should combine several independent controls:
- Access-token lifetime
- Refresh-token idle timeout
- Absolute session lifetime
- Authentication-strength requirements
- Operation-level step-up authentication
- Session revocation
- Device or key trust
- Transaction risk
Suppose a user has a 30-day refresh capability. That should not mean the session is unconditionally trusted for 30 days.
A session may continue for low-risk browsing while a beneficiary addition, payout, account recovery, or security-setting change requires recent authentication. If the session suddenly changes network, device key, application-integrity state, or behavioural pattern, the backend can reduce privileges or require step-up authentication without necessarily destroying the session.
A useful risk response model is graduated:
- Low risk: continue normally.
- Elevated risk: shorten effective idle expiry or require additional proof.
- High risk: block sensitive operations and require step-up authentication.
- Critical risk: revoke the affected session or broader token families and trigger recovery according to policy.
Risk should influence policy, not mutate a signed access token already in circulation. The backend can instead reject the token for selected operations, issue a replacement with reduced scope, or require a new authentication event.
Signals must also be explainable. IP address changes are common on mobile networks. Location can be imprecise. Device integrity can be temporarily unavailable. A single weak signal should rarely produce a permanent account lock.
Design-Time Controls
Session security begins before runtime signals exist.
The architecture should define:
- A session threat model
- Token purposes and audiences
- Maximum compromise windows
- Idle and absolute expiration rules
- Refresh rotation or sender-constraint behaviour
- Per-device and account-wide revocation semantics
- Recovery and device-rebinding flows
- Step-up requirements by operation
- Platform storage classifications
- Key and signing-certificate rotation
- Behaviour during authorization-service outages
- Backward compatibility across mobile versions
- Audit retention and privacy boundaries
Mobile backward compatibility deserves particular attention. An older application may not understand a new refresh error, proof format, or mandatory integrity requirement. Security-policy rollout should therefore support capability negotiation, minimum-version enforcement, staged adoption, and observable fallback behaviour.
Runtime Controls
Runtime enforcement should include:
- Transport encryption for every credential-bearing request
- Strict access-token validation
- Atomic refresh rotation when rotation is used
- Refresh-reuse detection for rotation-based public-client sessions
- Rate limits for login, refresh, OTP, and recovery
- Replay protection for signed request proofs
- Fresh server-generated challenges where the integrity protocol requires them
- Session-status checks on high-risk endpoints
- Key rotation with controlled overlap
- Structured security events
- Monitoring for abnormal session creation and refresh patterns
- User-visible device and session management
- Remote revocation capability
- Best-effort removal or invalidation of local session material
Logs should include identifiers, result codes, risk decisions, and revocation reasons only to the extent required and permitted by retention policy. Raw access tokens, refresh tokens, OTPs, private keys, and full authorization headers must not be logged.
Failure Modes Teams Commonly Miss
Refresh storms
Several mobile requests encounter an expired token simultaneously, causing multiple refresh attempts. Without client coordination and atomic server rotation, users may be unexpectedly logged out, refresh-token branches may diverge, or reuse handling may become unreliable.
Offline expiry
A user opens the application without connectivity. The UI may still show cached data even though the application cannot establish current server authorization. The product must distinguish locally cached signed-in state from authorization for a new server operation.
Sensitive cached content may require local encryption, screen protection, an explicit offline-expiry policy, or local reauthentication before display. These measures reduce exposure but do not create fresh server authorization.
Crash between rotation and persistence
The server returns a new refresh token, but the application crashes before securely persisting it. The old token has already been invalidated.
Possible mitigations include coordinated local persistence, a narrowly constrained retry mechanism, or a carefully designed recovery exchange. Indefinitely accepting both tokens would substantially weaken rotation.
Revocation-state outage
If services fail open, revoked sessions may regain access. If they fail closed, an infrastructure incident can block every user.
The correct choice depends on the operation. Reading a public catalogue and initiating a financial transfer should not share the same failure policy.
Clock skew
Device time should not be the authority for access-token expiry. Resource services should evaluate token time claims using trusted server time and only a small, explicitly configured tolerance. Proof protocols that accept client creation times should separately bound proof age and may use server-provided nonces to reduce reliance on the client clock.
Push token confusion
A push-notification token identifies a messaging destination. It is not an authenticated session, user-presence signal, or device-binding secret and must not be accepted as one.
Session resurrection
A logout request succeeds, but a background worker later reads stale local credentials and refreshes the session. Local cleanup, background task cancellation, and server-side revocation must be coordinated.
Common Misconceptions
“JWT means stateless authentication”
JWT validation can be local, but production session management often retains state for logout, device management, refresh rotation, compromise detection, role changes, and risk decisions.
“A refresh token should never expire”
Long-lived does not mean permanent. Refresh tokens need a defined inactivity policy, revocation behavior, and recovery rules. An absolute lifetime is a deployment choice based on risk and reauthentication requirements.
“Secure storage prevents token theft”
Secure storage makes extraction harder. It does not prevent misuse through a compromised runtime, malicious accessibility control, instrumentation, screen takeover, or an already-unlocked device.
“Device binding proves the user is present”
Key binding demonstrates that the caller can use a private key, subject to the limits of the key environment and proof protocol. User presence requires a separate control, such as a user-authentication-gated key operation, device credential, passkey ceremony, or server-side step-up.
“Logout only needs local deletion”
That stops the legitimate application from using its copy. It does not invalidate copied credentials or another active client.
“Short access tokens solve revocation”
They bound part of the exposure window, but they do not revoke refresh capability, immediately correct stale authorization, or protect a high-value operation during the token’s remaining lifetime.
A Post-Mortem Pattern Worth Recognizing
A representative fintech incident begins with intermittent forced logouts after access-token expiry. Monitoring initially suggests stolen refresh tokens because the same refresh credential appears twice.
The actual cause is a mobile networking layer that allows every failed API call to initiate its own refresh request. Two requests reach different backend instances almost simultaneously. One rotates the token; the second is classified as reuse and the configured policy revokes the family.
The immediate fix is client-side single-flight refresh coordination. The durable fix also includes atomic server rotation, structured reuse reasons, correlation IDs, and a narrowly documented policy for ambiguous duplicate requests.
The lesson is that a security control cannot be evaluated separately from concurrency and failure recovery. Rotation without coordination can punish legitimate users. Coordination without atomic enforcement leaves a replay gap.
Practical Architecture Checklist
Before shipping, confirm that:
- The server owns the session or grant lifecycle where revocation and risk policy require state.
- Access and refresh tokens have separate purposes.
- Access tokens are audience-restricted and have a risk-appropriate bounded lifetime.
- Refresh tokens issued to public clients are sender-constrained or rotated, protected at rest, and revocable.
- Rotation-based refresh operations are serialized where possible on the client and atomic on the backend.
- Reuse detection has an explicit response policy that accounts for retries and races.
- Device binding uses cryptographic keys rather than mutable identifiers.
- Integrity signals are verified server-side and treated as risk inputs.
- Logout revokes server-side state when reachable and clears local material.
- Per-device and all-device logout are different operations.
- Idle and absolute expiration are enforced independently.
- Sensitive actions can require recent or stronger authentication.
- JWT validation checks the algorithm policy, signature, type, issuer, audience, time claims, and required authorization claims.
- Role and account changes have a defined propagation strategy.
- Offline behaviour and cached-data exposure are documented.
- Keys, tokens, OTPs, and authorization headers never enter logs.
- Recovery, reinstall, key loss, and device migration have tested flows.
- Security controls have observable failure reasons and operational runbooks.
A Realistic Deployment Scenario
Consider a merchant payments application used across personal phones, managed field devices, and unreliable mobile networks.
The architecture issues bounded-lifetime access tokens and maintains one server-side session per app installation. Each refresh-capable session belongs to a token family and an app-generated public key. Normal catalogue and profile operations accept locally validated access tokens until expiry. Settlement, beneficiary, and account-security endpoints additionally check current session status and require a fresh proof from the session-bound key.
When a network change raises risk slightly, the application continues normal operation. When a different device key appears for a session, the backend rejects key-bound financial requests and requires an authenticated recovery or new-session flow; it does not silently rebind the session. When refresh-token replay is sufficiently established under the configured policy, the token family is revoked. When the merchant selects “logout all devices,” every server-side session is marked revoked; sensitive endpoints observe that state immediately, while endpoints relying only on local token validation reject existing tokens no later than their expiry.
This design does not assume that tokens, mobile devices, or integrity systems are perfect. It limits how far each failure can propagate and gives the platform explicit ways to recover.
Mobile Session Architecture: Tokens, Refresh, Device Binding, Logout, and Risk-Based Expiry was originally published in ProAndroidDev on Medium, where people are continuing the conversation by highlighting and responding to this story.