After a user signs in, your app holds tokens: a short-lived access token it sends with each request, and usually a longer-lived refresh token it uses to get new access tokens without making the user log in again. Those tokens are the keys to the user's session, so where you store them and how you handle their lifecycle is a real security decision. Store them in the wrong place and a compromised device or backup leaks them; handle refresh badly and a stolen refresh token grants long-term access. Here is how to store tokens securely and manage the access-and-refresh lifecycle so a leak is contained.
Short answer
Tokens should be stored in the platform's secure storage, the iOS Keychain or Android Keystore-backed storage, never in plaintext locations like UserDefaults, SharedPreferences, or files. Per OWASP, access tokens should be short-lived to limit the exposure window, while the longer-lived refresh token is high-value and warrants extra care, including refresh token rotation, issuing a new refresh token on each use, invalidating the old one, and detecting reuse to catch theft. Transmit tokens only over TLS, keep them out of logs, URLs, and analytics, clear them on logout and revoke server-side, and prefer sender-constrained tokens where available so a stolen token is not usable elsewhere. The aim is that tokens are protected at rest, short-lived where possible, and revocable, so a leak is limited.
What you should know
- Store tokens in secure storage: Keychain or Keystore, not plaintext.
- Keep access tokens short-lived: to limit the exposure window.
- The refresh token is high-value: protect it and rotate it.
- Rotate refresh tokens and detect reuse: to catch token theft.
- Clear on logout and revoke server-side: do not just delete locally.
Where do you store tokens?
In the platform's secure storage, not in convenient plaintext stores. Tokens are secrets, so they belong in the iOS Keychain or in Android storage backed by the Keystore, which protect them with the device's secure facilities, rather than in places that store data in plaintext. The common mistake is putting a token in UserDefaults on iOS or SharedPreferences on Android, or in a plain file, all of which keep it readable in plaintext where it can be recovered on a compromised or jailbroken device and swept into backups. So the first decision is simply location: a token is a credential, and credentials go in secure storage. Beyond where they rest, tokens must also be handled carefully in transit and in passing: send them only over TLS, never put them in URLs, where they can end up in logs and history, and keep them out of application logs and analytics, since a token that leaks anywhere is a token an attacker can use. Secure storage protects the token at rest; disciplined handling keeps it from leaking elsewhere.
How do access and refresh tokens differ, and what is rotation?
By lifetime and role, which shapes how you protect each. The table summarizes.
| Aspect | Access token | Refresh token |
|---|---|---|
| Lifetime | Short-lived | Longer-lived |
| Use | Sent with each request | Exchanged for new access tokens |
| Exposure window | Limited by short lifetime | Larger, so higher value |
| Rotation | Reissued frequently by design | Rotated on each use, old one invalidated |
| Theft detection | Limited by expiry | Reuse of an old token signals theft |
An access token is short-lived and accompanies each request, so keeping its lifetime short limits how long a leaked one is useful, expiry does much of the work of containing exposure. A refresh token lives longer and is exchanged for new access tokens, which makes it higher value: a stolen refresh token can grant continued access, so it deserves more protection. Refresh token rotation addresses this: each time a refresh token is used, the server issues a new one and invalidates the old, so a refresh token is single-use. The security payoff is theft detection, if an old, already-rotated refresh token is presented again, that signals it was stolen and replayed, and the server can revoke the whole token family, cutting off the attacker and the legitimate client so the user re-authenticates. So the two token types call for different handling: short lifetimes for access tokens, and rotation with reuse detection for refresh tokens.
How do you handle tokens securely across their lifecycle?
Protect them at rest, in transit, and across issuance, refresh, and revocation. Store both tokens in secure storage, keep access-token lifetimes short, and use refresh token rotation with reuse detection so a stolen refresh token is caught and the family revoked. Transmit tokens only over TLS and never log them, embed them in URLs, or send them to analytics. On logout, clear the tokens from secure storage locally and revoke them server-side, so the session genuinely ends rather than the tokens remaining valid; the server, as the authority, must be able to revoke a session, and you should rely on that rather than assuming a local delete is enough. Scope tokens to least privilege so a leaked token grants only what it must, and prefer sender-constrained or device-bound tokens where your platform and provider support them, so a token stolen from one device cannot be used from another. Keep verification on the server: the backend validates every token, since the app is one client and an attacker can present a token directly. The principle is that tokens are protected secrets with a managed lifecycle, short-lived, rotated, revocable, and bound where possible, so that even a leaked token has limited value and a short life.
What to watch out for
The first trap is storing tokens in UserDefaults, SharedPreferences, or a plain file, where they sit in plaintext and leak from a compromised device or backup; use secure storage. The second is long-lived access tokens and refresh tokens without rotation, so a single leak grants lasting access; shorten access lifetimes and rotate refresh tokens with reuse detection. The third is logout that only deletes locally without server-side revocation, and tokens leaking through URLs, logs, or analytics. Token handling spans app and server, so a pre-submission scan such as PTKD.com (https://ptkd.com), which reads the binary against OWASP MASVS, surfaces how your app stores data and handles authentication, flagging tokens kept insecurely, while rotation and revocation are yours to implement on the backend.
What to take away
- Store access and refresh tokens in the platform's secure storage, the Keychain or Keystore-backed storage, never in plaintext locations like UserDefaults, SharedPreferences, or files.
- Keep access tokens short-lived to limit exposure, and protect the higher-value refresh token with rotation, issuing a new one on each use and detecting reuse to catch theft and revoke the family.
- Transmit tokens only over TLS, keep them out of URLs, logs, and analytics, clear them on logout and revoke server-side, scope to least privilege, and prefer device-bound tokens where supported.
- Use a pre-submission scan such as PTKD.com to surface insecure token storage and authentication handling, and implement rotation and revocation on the backend.



