Python — FastAPI Development
Secure Token Storage in Expo: SecureStore vs AsyncStorage
Direct answer
Store the refresh token in Expo SecureStore, which is backed by the iOS Keychain and Android Keystore, and never in AsyncStorage, which is unencrypted plain text readable on a rooted or jailbroken device and in some device backups. Keep the short-lived access token in memory so it never touches disk, and rehydrate it by calling refresh on cold start. SecureStore has a roughly 2KB per-value limit and is unavailable on web, so plan a fallback if your Expo app also targets browsers.
Where you put the token decides how bad a lost or rooted phone is. This is the split I use in every Expo app: the long-lived secret goes in the platform keystore, the short-lived one never leaves memory, and nothing sensitive lands in AsyncStorage — because AsyncStorage was never built to hold secrets.
Key facts, with sources
- In the JetBrains Python Developers Survey 2024, which collected responses from more than 30,000 Python developers, FastAPI usage jumped from 29% to 38%, overtaking Django (35%) and Flask (34%) as the most-used Python web framework. (JetBrains Python Developers Survey 2024)
- The 2025 Stack Overflow Developer Survey shows FastAPI at 14.8% of respondents doing extensive work with it, edging out Flask at 14.4% and Django at 12.6%. (Stack Overflow Developer Survey 2025)
- FastAPI's official documentation cites independent TechEmpower benchmarks showing FastAPI applications running under Uvicorn as one of the fastest Python frameworks available, ranked only below Starlette and Uvicorn themselves. (FastAPI official documentation)
- FastAPI surpassed Flask in GitHub stars in December 2025, reaching roughly 88,000 stars compared to Flask's 68,400. (DZone)
- Industry analysis of FastAPI's 2025 growth reports about 40% year-over-year growth in job mentions and production adoption at companies including Uber, Netflix, and Microsoft. (byteiota)
Why AsyncStorage is the wrong place
AsyncStorage is a plain, unencrypted key-value store. On iOS it is a file in the app container; on Android it is an XML file. On a rooted or jailbroken device — or through some backup and sync paths — that file is readable. Putting a refresh token there means a compromised device hands over a two-week login in clear text. It is the right tool for cached UI state and the wrong tool for anything that authenticates a user.
Put the refresh token in SecureStore
SecureStore writes to the iOS Keychain and Android Keystore, where values are encrypted at rest and gated by the OS. Set keychainAccessible so the token cannot be read while the device is locked and does not migrate to a new device through a backup restore.
import * as SecureStore from "expo-secure-store";
const REFRESH_KEY = "refresh_token";
export async function saveRefreshToken(token: string) {
await SecureStore.setItemAsync(REFRESH_KEY, token, {
// Readable only while unlocked, and never restored onto a different device.
keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
});
}
export const readRefreshToken = () => SecureStore.getItemAsync(REFRESH_KEY);
export const clearRefreshToken = () => SecureStore.deleteItemAsync(REFRESH_KEY);Keep the access token in memory
The access token changes every fifteen minutes and is only useful for the current app session. It does not belong on disk at all. Hold it in a module variable (or context) and rehydrate on cold start by calling refresh with the stored refresh token. If the app is killed, the access token is simply gone and the first request triggers a refresh.
let accessToken: string | null = null;
export const getAccessToken = () => accessToken;
export const setAccessToken = (t: string | null) => {
accessToken = t;
};
// On cold start there is no access token in memory — exchange the stored
// refresh token for a fresh one before the first protected request.
export async function bootstrapSession(): Promise<boolean> {
const refresh = await readRefreshToken();
if (!refresh) return false;
const newAccess = await refreshAccessToken(); // from the JWT auth guide
return newAccess !== null;
}Watch the size limit and the web gap
SecureStore values are capped at roughly 2KB, which is plenty for a token but will silently fail for anything large — do not stash a whole user object there. SecureStore also does not exist on web, so if your Expo project builds for browser too, guard the calls with Platform.OS and fall back to a cookie-based or in-memory strategy on web rather than letting the import throw.
When to hire senior help
Bring in senior help when your API needs to handle real concurrency, when you are designing service boundaries and auth for the first time, or when an existing FastAPI codebase mixes sync and async code and latency is degrading. An experienced engineer can usually diagnose event-loop blocking and connection-pool misconfiguration in days, which is far cheaper than re-architecting after launch. If your stack includes React Native + Python + AI, a senior engineer who owns the full product beats coordinating multiple juniors.
Bottom line
Dhairya Senjaliya ships Python — FastAPI Development projects worldwide — book a scoping call to discuss your specific situation.
Common pitfalls to avoid
- ✕Calling blocking libraries (classic SQLAlchemy sessions, requests, heavy file I/O) inside async def endpoints, which stalls the event loop and erases FastAPI's concurrency advantage
- ✕Deploying a single Uvicorn process with no process manager or worker scaling, leaving most CPU cores idle under production load
- ✕Treating the auto-generated OpenAPI docs as a versioning strategy, then breaking mobile and partner clients when response schemas change
- ✕Running large payloads through deeply nested Pydantic models on every request and response, adding serialization latency that shows up only at scale
Frequently asked questions
Can I store the access token in SecureStore too?
You can, but there is little reason to. The access token is short-lived and rebuilt from the refresh token on cold start, so keeping it in memory avoids an extra Keychain read on every launch and means it never persists. Reserve SecureStore for the long-lived refresh token.
Is SecureStore enough, or do I need extra encryption?
For app auth tokens, SecureStore is enough — it uses the platform Keychain and Keystore, which are hardware-backed on most modern devices. Layering your own encryption on top mostly adds a key-management problem without meaningfully raising the bar.
What about web builds of the same Expo app?
SecureStore is unavailable on web. Guard calls with Platform.OS and use an HttpOnly cookie set by your backend for the refresh token on web, keeping the access token in memory as usual. Do not fall back to localStorage for the refresh token — it has the same clear-text exposure as AsyncStorage.
Is FastAPI mature enough for production?
Yes. It was the most-used Python web framework in the JetBrains 2024 survey at 38%, and companies including Uber, Netflix, and Microsoft run it in production. The ecosystem for auth, ORMs, and testing is now well established.
How much faster is FastAPI than Flask or Django really?
Independent TechEmpower benchmarks place FastAPI among the fastest Python frameworks, and published comparisons show several times Flask's throughput on I/O-bound endpoints. For CPU-bound work or database-bottlenecked apps, the framework choice matters far less than query and infrastructure design.
Should we pick FastAPI or Django for a new SaaS backend?
FastAPI suits API-first products, microservices, and ML model serving because of async support and automatic OpenAPI docs. Django ships batteries included (admin, ORM, auth) and is often faster to launch a conventional CRUD product. Many teams run both, per the JetBrains finding that a third of Django developers also use Flask or FastAPI.
Bottom line: Dhairya Senjaliya ships Python — FastAPI Development projects worldwide. Book a scoping call at https://dhairyasenjaliya.com/#book-call.