Most of the mobile-adjacent apps I've poked at started life as a normal web app and then got squeezed onto a phone. Maybe it's wrapped in a thin native shell. Maybe it's just a responsive site people add to their home screen. Maybe there's no wrapper at all, just a single page that happens to look good on a six-inch screen. What they all have in common is this: somewhere along the way, the assumption that there's "a browser session" quietly stopped being true, and nobody went back and updated the security model to match.
That assumption does more work than most teams realize. A normal web app leans on the browser for things it never has to think about: cookies the browser refuses to hand to another site, a session that dies when the tab closes, a rough sense of where a request came from. Take the browser out of the picture, or reduce it to a wrapper that's really just a window onto your API, and all of those quiet protections go with it. Nobody removed them on purpose. They just weren't there to begin with.
What actually changes
Most mobile-adjacent apps end up storing an auth token somewhere the app's own JavaScript can read it, because a WebView shell often doesn't get the same cookie handling a real browser tab gives you, and because a lot of teams want one token that works whether the call comes from the phone browser or from inside the shell. That's a reasonable engineering call. It also means the token isn't protected by cookie rules anymore. A single XSS bug on that page stops being an annoying popup and becomes a full account takeover, because the thing sitting in local storage is the entire key to the account.
The other change is quieter and matters more: your server can no longer assume a request came from your own front end just because the front end would never send it that way. There's no origin, no referer, no browser tab to trust. Every request has to be authenticated and authorized on its own, using whatever claims are actually inside the token, because the client making that request might not be your app at all. It might be someone who pulled the API calls straight out of the app package.
The checklist
- Authorize on every request using the token's own claims, never on the assumption that a request came from your UI.
- Never put an auth token in a URL or query string. Deep links, share sheets, and server logs will all quietly keep a copy of it.
- Treat the API as public. Assume anyone can call it directly with a token pulled out of the app, because they can.
- Give tokens a short life and build real refresh and rotation. Don't reach for a token that never expires because refresh logic felt like a chore.
- Build a way to kill a session from the server side. A lost or stolen phone needs a remote kill switch, not a hope that the user logs out first.
- Check what your per-user data filtering is actually keyed on. It should be the user id from the verified token, not a session object that only ever existed in a browser.
- If it's a PWA with a service worker, make sure authenticated API responses aren't sitting in a cache the next person on that phone can open.
- Rate-limit and log at the API, not the app. The UI can't stop a scripted client from hammering an endpoint. Only the server can.
- Assume the app shell will get decompiled. Pulling API routes and keys out of a WebView wrapper takes about as long as pulling them out of any APK.
The two worth sitting with
Of that list, two items cause the most actual damage when skipped. The first is token storage plus XSS. On a normal site, a stray XSS bug is bad but usually contained by httpOnly cookies. Once the token lives in storage your own scripts can read, that containment is gone, and one bad third-party widget or one unescaped field is the whole account, not just a corner of it.
The second is authorization that only exists in the client's head. This is the same failure mode behind the 2025 Lovable and Supabase disclosure, where a row-level security setting that was supposed to filter every query by user id simply wasn't wired up correctly on the backend. Scans at the time found the problem in over 170 live production apps. Nobody had to break in. The backend just handed back data it should never have handed back, because it trusted that the right question had already been asked upstream. A mobile-adjacent app with no browser session guardrails is exactly the shape of product where that same mistake is easiest to make and slowest to notice, because there's no browser tab quietly enforcing boundaries you forgot to write down.
If I had to compress this whole list into one question, it would be: what happens the moment someone's phone gets lost. Not stolen by a sophisticated attacker, just left in a cab. If the honest answer involves a shrug, that's the gap worth closing first, because it's usually a sign the rest of the list hasn't been thought through either.