<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Srinivas Mekala</title>
    <description>The latest articles on DEV Community by Srinivas Mekala (@srinivas1227).</description>
    <link>https://dev.to/srinivas1227</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2471430%2Fcc8d30bf-9e0a-4ff1-90f5-54e8171869b6.jpeg</url>
      <title>DEV Community: Srinivas Mekala</title>
      <link>https://dev.to/srinivas1227</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/srinivas1227"/>
    <language>en</language>
    <item>
      <title>Cookies or Bearer Tokens? The Day my Auth Design Decision Broke.</title>
      <dc:creator>Srinivas Mekala</dc:creator>
      <pubDate>Sat, 11 Jul 2026 16:20:13 +0000</pubDate>
      <link>https://dev.to/srinivas1227/cookies-or-bearer-tokens-the-day-my-auth-design-decision-broke-178l</link>
      <guid>https://dev.to/srinivas1227/cookies-or-bearer-tokens-the-day-my-auth-design-decision-broke-178l</guid>
      <description>&lt;p&gt;It's been 3 months since I was actively building &lt;a href="https://mystashd.link" rel="noopener noreferrer"&gt;StashD&lt;/a&gt; -- a link-organizing app. NextJs on the frontend, Flask + MongoDB on the backend. Auth was one of the first things I had to get right, so I did what felt like the "correct" thing at the time: httpOnly cookies. Secure, invisible to JavaScript, browser handles everything for me. I shipped it, moved on, felt good about the decision.&lt;/p&gt;

&lt;p&gt;Today, I thought, why don't I build a mobile app so my users can access their saved links from their phones on demand.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Moment It Fell Apart
&lt;/h2&gt;

&lt;p&gt;I sat down to think through how the mobile app would authenticate, researched a lil bit, and it hit me in the face: &lt;strong&gt;there's no browser cookie jar on a native app🤦🏻‍♂️.&lt;/strong&gt; My entire auth flow assumed a browser sitting between the user and my API — one that would store a cookie and automatically reattach it on every request without me writing a line of code for it. That assumption had been invisible to me the whole time I was building for web only, because it was just... always true. Every request from the browser already carried the cookie. I never had to think about the mechanism, so I never noticed I'd built a dependency on it.&lt;/p&gt;

&lt;p&gt;A mobile app doesn't get that for free. There's no automatic anything. If I wanted the Android app to authenticate against the same Flask API, I'd need to manually attach a token to every request myself. Which meant my whole "cookie is the auth mechanism" design was actually "cookie is &lt;em&gt;a&lt;/em&gt; auth mechanism, coupled to exactly one client — the browser."&lt;/p&gt;

&lt;p&gt;That was the first real &lt;strong&gt;lesson&lt;/strong&gt;: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An auth design that only works because of an environment-specific behavior isn't a design, it's an accident that happens to work in one place.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  It's Time to Rewind the Reel
&lt;/h2&gt;

&lt;p&gt;I had to stop and actually understand &lt;em&gt;why&lt;/em&gt; I picked cookies in the first place, instead of just knowing that I had. Here's what I worked out.&lt;/p&gt;

&lt;p&gt;A cookie and a bearer token carry the same thing — a JWT. The difference is entirely about &lt;em&gt;who attaches it to a request&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cookie&lt;/strong&gt;: the browser attaches it automatically, for every request matching the cookie's domain. I don't have to write the attachment logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bearer token&lt;/strong&gt;: my code does it, manually, via an &lt;code&gt;Authorization: Bearer &amp;lt;token&amp;gt;&lt;/code&gt; header. Nothing happens unless I write that line.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. That's the whole difference. But that one difference cascades into everything else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Did I pick Cookies blindly? No!:&lt;/strong&gt; _httpOnly _cookies can't be read by page JavaScript. If someone ever manages to inject a malicious script into my site (XSS), that script &lt;em&gt;cannot&lt;/em&gt; steal the token — it's invisible to &lt;code&gt;document.cookie&lt;/code&gt;. If I had instead put my JWT in &lt;code&gt;localStorage&lt;/code&gt;, any XSS bug becomes a full account takeover, and the stolen token is portable — usable from the attacker's own machine, forever, until it expires.&lt;/p&gt;

&lt;p&gt;That's a real, good reason to prefer cookies. I took it as "best practice" without connecting it to the actual mobile-shaped hole in the design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But cookies aren't free.&lt;/strong&gt; Because the browser attaches them automatically, a malicious site can trick a victim's browser into firing a request at my API with the cookie riding along — that's CSRF. Next.js comes to the rescue here: it defaults cookies to &lt;code&gt;SameSite=Lax&lt;/code&gt;, which blocks that for state-changing requests.&lt;/p&gt;

&lt;p&gt;Bearer tokens flip both properties. Immune to CSRF by construction — there's no ambient credential for a third-party site to hijack, since nothing is attached automatically. But if the token sits in &lt;code&gt;localStorage&lt;/code&gt; or a JS variable, XSS &lt;em&gt;can&lt;/em&gt; read it directly, and now it's stolen, not just abused-in-session.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Neither one is "more secure" in the abstract. They're secure against &lt;em&gt;different things&lt;/em&gt;, and the win depends entirely on the shape of your client.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  My Second Lesson: What do I change to support both mobile and web requests?
&lt;/h2&gt;

&lt;p&gt;My first plan was: keep cookies for web, add a second code path in Flask that also accepts a bearer header for mobile. Check cookie first, fall back to header. Felt like the minimal change.&lt;/p&gt;

&lt;p&gt;It would have worked. But sitting with it a bit longer, I realized I was about to bake "what kind of client is calling me" into my backend's auth logic, permanently, for every route, forever. Two paths to test, two paths to audit for security holes, and the set only grows if I ever add a third client.&lt;/p&gt;

&lt;p&gt;The better shape, once I saw it: &lt;strong&gt;the backend shouldn't know or care what kind of client is calling it.&lt;/strong&gt; Make the API bearer-only, full stop. It returns a token in the response body on login, and validates one thing — the &lt;code&gt;Authorization&lt;/code&gt; header — on every protected route. No branching.&lt;/p&gt;

&lt;p&gt;Then the &lt;em&gt;cookie&lt;/em&gt; isn't a backend concern at all anymore. It becomes something the &lt;strong&gt;web app does to itself&lt;/strong&gt; — my Next.js server receives that bearer token same as any other client would, and wraps it in its own httpOnly cookie before it ever reaches the browser. When the web server needs to call the API on the user's behalf, it just reads its own cookie back out and forwards it as a bearer header. The Flask API never even knows a cookie existed anywhere in the chain.&lt;/p&gt;

&lt;p&gt;The mobile app does the same thing, minus the cookie step — takes the token from the response body, drops it straight into secure device storage (Keystore-backed on Android), attaches it as a bearer header on every call. No intermediary needed, because a native app doesn't have the XSS threat model a browser has in the first place.&lt;/p&gt;

&lt;p&gt;Two clients, two completely different ideas of "how do I protect this token" — and the backend doesn't have to know either one exists. That's the part I actually feel good about: not "I added mobile support," but "I found the layer where the client-specific decision actually belongs, and moved it there."&lt;/p&gt;




&lt;p&gt;PS: Try out my WebApp : &lt;a href="https://mystashd.link" rel="noopener noreferrer"&gt;StashD&lt;/a&gt;.&lt;br&gt;
Hope you like it and wait for the mobile version too.🫡✌🏻&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>mobile</category>
      <category>security</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
