Python — FastAPI Development

JWT in Cookies vs Bearer Tokens for React Native and Web

Direct answer

Use bearer tokens in the Authorization header for a React Native app, because native apps have no browser cookie jar and store the token securely in the Keychain/Keystore instead. Use httpOnly cookies for a web client, because they are invisible to JavaScript and so cannot be stolen by XSS — at the cost of needing CSRF protection. When one FastAPI backend serves both, the cleanest design is to support both transports: cookies for the web origin, bearer tokens for the mobile app, sharing the same token-issuing and validation logic.

Where the token rides — in a cookie or an Authorization header — is a security decision, not a style choice, and the right answer depends on the client. The confusion starts when teams apply web reasoning ("httpOnly cookies are safer") to a React Native app, where cookies barely apply. Here is how the two transports actually differ and how to serve both from one API.

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)

The core trade-off: XSS vs CSRF

A bearer token stored in JavaScript-reachable storage (or even in memory) can be exfiltrated if your web app has an XSS hole — the script reads the token and sends it away. An httpOnly cookie is invisible to JavaScript, so XSS cannot read it — but because the browser attaches it automatically to every request to your domain, it is vulnerable to CSRF unless you add protection (SameSite, a CSRF token). So the choice is really: which attack are you defending against, and does your client even have a browser.

React Native: bearer tokens win by default

A native React Native app is not a browser. It has no automatic cookie jar, no same-origin policy, and no XSS surface in the web sense. Storing a token in Expo SecureStore (Keychain/Keystore) and sending it as an Authorization header is both simpler and appropriate. Trying to force httpOnly cookies into a native app means fighting the platform for a protection (XSS-immunity) you do not need.

React Native — bearer token
import * as SecureStore from "expo-secure-store";

api.interceptors.request.use(async (config) => {
  const token = await SecureStore.getItemAsync("accessToken");
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});

Web: httpOnly cookies are usually safer

For a browser client, an httpOnly, Secure, SameSite cookie keeps the token out of reach of any injected script — the single biggest token-theft vector on the web. The trade is that you now defend CSRF, mostly handled by SameSite=Lax or Strict plus a CSRF token on state-changing requests. FastAPI can set the cookie on login and read it on protected routes.

FastAPI — httpOnly cookie for web
@router.post("/auth/login-web")
def login_web(body: LoginIn, response: Response, db: Session = Depends(get_db)):
    user = authenticate(db, body.email, body.password)
    if not user:
        raise HTTPException(401, "invalid credentials")
    response.set_cookie(
        "access_token", mint_access_token(user.id),
        httponly=True, secure=True, samesite="lax", max_age=900,
    )
    return {"message": "signed in"}

Serving both from one FastAPI backend

When the same API backs a mobile app and a web app, do not pick one transport and compromise the other. Accept the token from either place: read the Authorization header if present, otherwise fall back to the cookie. The token itself, its signing, expiry, and refresh logic are identical — only the transport differs per client. This keeps the mobile app on bearer tokens and the web app on httpOnly cookies without duplicating any auth logic.

deps.py — accept either transport
from fastapi import Request, HTTPException

def get_token(request: Request) -> str:
    auth = request.headers.get("Authorization")
    if auth and auth.startswith("Bearer "):
        return auth.split(" ", 1)[1]         # mobile: bearer token
    cookie = request.cookies.get("access_token")
    if cookie:
        return cookie                        # web: httpOnly cookie
    raise HTTPException(401, "not authenticated")

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

Is it safe to store a JWT in AsyncStorage or localStorage?

Not for a long-lived token. Both are readable by JavaScript, so an XSS bug on web or a compromised device on mobile exposes them. On React Native use Expo SecureStore; on web prefer an httpOnly cookie for the refresh token and keep the access token in memory.

Do React Native apps need CSRF protection?

No, if you use bearer tokens. CSRF exploits the browser automatically attaching cookies; a native app sending an explicit Authorization header is not subject to it. CSRF only becomes your concern on the web-cookie side.

Can I just use bearer tokens everywhere, including web?

You can, and many APIs do for simplicity, but on the web it means the token lives somewhere JavaScript can reach, so you inherit XSS risk. If your web app has a disciplined XSS posture and you keep the access token in memory, bearer-everywhere is a reasonable trade for a single, simpler auth path.

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.

Sources

Related guides

Keep up with new guides

New deep-dive guides on React Native, Python, and AI ship regularly. Subscribe via RSS or follow on LinkedIn.

Want help implementing this?

30-minute scoping call · Clear milestones · Senior engineer ownership