<?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: Jishant Sethi</title>
    <description>The latest articles on DEV Community by Jishant Sethi (@jishantsethi).</description>
    <link>https://dev.to/jishantsethi</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%2F4045922%2F8faed6a7-e414-4f21-9fc9-d9462ba4e952.png</url>
      <title>DEV Community: Jishant Sethi</title>
      <link>https://dev.to/jishantsethi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jishantsethi"/>
    <language>en</language>
    <item>
      <title>Token != Stateless</title>
      <dc:creator>Jishant Sethi</dc:creator>
      <pubDate>Fri, 24 Jul 2026 18:12:39 +0000</pubDate>
      <link>https://dev.to/jishantsethi/token-stateless-7k4</link>
      <guid>https://dev.to/jishantsethi/token-stateless-7k4</guid>
      <description>&lt;h2&gt;
  
  
  What a Mobile Developer Learned Diving Into Cookies, Sessions, and Auth
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;A React Native developer's journey from "how do I keep a user logged in?" to the one distinction that made web auth finally click, plus a strong (and debatable) opinion on when to use each.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Back in 2022, I got an opportunity I am still grateful for: working on a real, live React Native project at my previous organization. It was the kind of project you learn a lot on. Along the way, as I picked up TypeScript, I also got my hands dirty with backend work in Node, and I ended up shipping a few backend projects I was genuinely proud of.&lt;/p&gt;

&lt;p&gt;I never fully let that go. Even after moving on, I kept building small and medium side projects in Node, partly to stay sharp and partly because I enjoy it. Recently I sat down to catch up on what has changed in the ecosystem, and, as these things go, I fell down a rabbit hole. It started with something that looks deceptively simple: cookies and sessions.&lt;/p&gt;

&lt;p&gt;I was not starting from zero. A few days earlier I had been revisiting the JWT, access token, and refresh token model from the mobile side, the stuff you deal with when your app talks to an API. So the vocabulary was not new. But I realized I understood it the way a lot of us do: I could use it, but I could not cleanly explain why it is built the way it is.&lt;/p&gt;

&lt;p&gt;So I did the deep dive. Blogs, YouTube, AI tools, the works. And somewhere in there, the whole topic collapsed into one pair of words that tied everything together: stateful and stateless.&lt;/p&gt;

&lt;p&gt;This post is me rephrasing that understanding in my own words, through the lens of a mobile developer, and taking a clear stance on the tradeoff at the end. I would genuinely love to hear where you agree or disagree. This is a topic where smart people land in different places.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem nobody explains first: HTTP forgets
&lt;/h2&gt;

&lt;p&gt;Before any of this makes sense, one fact has to land: HTTP is stateless. Every request your browser or app sends is treated as if it is the first time the server has ever heard from you. The server handles it, sends a response, and then forgets you completely. The next request is a total stranger knocking at the door.&lt;/p&gt;

&lt;p&gt;That is the whole problem. If the server forgets you after every request, how does it know, on your next request, that you already logged in a moment ago?&lt;/p&gt;

&lt;p&gt;Everything that follows (cookies, sessions, tokens, the entire dance) exists to answer that single question. Once I framed it that way, the topic stopped feeling like trivia and started feeling like a series of deliberate design decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three ways to make the server remember you
&lt;/h2&gt;

&lt;p&gt;The naive idea is to store the answer in a cookie, literally &lt;code&gt;loggedIn=true&lt;/code&gt;, and let the browser send it back on every request. It works right up until the user opens DevTools and edits it. The data is sitting in the user's hands, so the user can rewrite it. Not acceptable.&lt;/p&gt;

&lt;p&gt;The fix is a session. Keep the real data on the server, and hand the browser only a long, random, signed ID, a meaningless pointer. On each request the browser sends the ID, the server looks it up, and finds "this ID belongs to user 5, who is logged in." The sensitive data never leaves the server, so there is nothing worth tampering with in the user's hands.&lt;/p&gt;

&lt;p&gt;The mental model that made this stick for me is a coat check. The session is your coat, kept safely behind the counter (the server). The cookie is just the numbered ticket in your pocket. The ticket does not contain your coat and it does not let you walk behind the counter. It only tells the staff which coat is yours. You can scribble a fake number on it, but it will not match any real coat, and you cannot guess a valid one because the real numbers are long and random.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdyf6k4k5xh0u45kfbhn5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdyf6k4k5xh0u45kfbhn5.png" alt=" " width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The third approach is a token, most often a JWT. Instead of a meaningless pointer, the token is a signed statement that carries your details inside it: "this is user 5, and here is the proof." The server does not need to look anything up. It just verifies the signature, pure math, and trusts it. That "no lookup" property is the piece that matters, and it leads straight to the idea that reframed the whole topic for me.&lt;/p&gt;

&lt;p&gt;(Worth knowing, because experts will ask: a JWT is only encoded, not encrypted. Anyone holding it can decode and read every value inside it. The signature does not hide the data, it only proves the data was not changed. So you put identity details in a token, never secrets.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffsmxp3tbkpbwusev9sx3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffsmxp3tbkpbwusev9sx3.png" alt=" " width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The insight: "token or session" is not the real question
&lt;/h2&gt;

&lt;p&gt;For a long time I had these mentally filed as two competing choices, sessions on one side, tokens on the other. That framing is wrong, and untangling it was the single most useful thing I got from this deep dive.&lt;/p&gt;

&lt;p&gt;There are really two separate questions here, and we usually jam them into one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;What is written on the thing the client sends?&lt;/strong&gt; Is it just a reference number that means nothing by itself, or does it actually carry the user's details inside it? A reference number is a &lt;strong&gt;session ID&lt;/strong&gt;. Think of a coat check ticket: the number 47 tells you nothing, it only points to locker 47. Details written on it make it a &lt;strong&gt;token&lt;/strong&gt;. Think of an ID card: your name and role are printed right there on the card.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;How does the server check that thing?&lt;/strong&gt; Does it look it up in its own records, or does it just verify a signature and trust it without storing anything? Looking it up is &lt;strong&gt;stateful&lt;/strong&gt;: the server remembers it. Verifying only the signature with no records is &lt;strong&gt;stateless&lt;/strong&gt;: the server does not remember it, it rechecks it fresh every time.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is the part that reframed everything for me. These two questions are independent. Whether something is stateful or stateless is decided only by the second question, the record keeping. It does not matter whether the credential is a token or a session ID. A token that the server looks up in a list is stateful. A token the server only signature checks is stateless. Same token, opposite model. The shape does not decide it, the record keeping does.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;
&lt;strong&gt;Stateless&lt;/strong&gt; &lt;em&gt;(no server record, verify by signature)&lt;/em&gt;
&lt;/th&gt;
&lt;th&gt;
&lt;strong&gt;Stateful&lt;/strong&gt; &lt;em&gt;(server keeps a record, verify by lookup)&lt;/em&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Token&lt;/strong&gt; &lt;em&gt;(carries data inside it)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;Plain JWT. Fast, easy to scale, but you cannot revoke it early. This is your &lt;strong&gt;access token&lt;/strong&gt;.&lt;/td&gt;
&lt;td&gt;JWT plus a server side allow or block list. Revocable. This is your &lt;strong&gt;refresh token&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Session ID&lt;/strong&gt; &lt;em&gt;(a pointer)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;(rare, not typical)&lt;/td&gt;
&lt;td&gt;Classic &lt;strong&gt;session&lt;/strong&gt; stored on the server. The default web setup.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;In practice a refresh token is often an opaque random string (a pointer), not a self contained JWT. Either way, what defines it is the right hand column: it is stateful, and therefore revocable.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And here is the kicker: the refresh token you already use in your mobile app is a stateful token. It is stored on the server, looked up when used, and can be revoked. You have been using both flavors this whole time without labeling them. The access token is the stateless one, the refresh token is the stateful one.&lt;/p&gt;

&lt;p&gt;So the real decision was never "token or session." It is "stateless or stateful," the storage model. "Token or session" is mostly about the credential's shape and how it travels.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this clicked through a mobile lens
&lt;/h2&gt;

&lt;p&gt;As a mobile developer, the fog cleared when I mapped it onto things I already knew.&lt;/p&gt;

&lt;p&gt;Within a single request, a backend often attaches data to a request object (like &lt;code&gt;req.user&lt;/code&gt; in Express) through middleware and passes it down the chain. That is exactly an Intent extra. It is transient, alive for this one trip, and gone afterward. It cannot carry anything to the next request, the same way an Intent extra does not survive to your next app launch.&lt;/p&gt;

&lt;p&gt;To persist across requests, you need something stored. On the web that is the cookie. On mobile, the closest equivalent is SharedPreferences, or, for anything sensitive, the Keystore, Keychain, or EncryptedSharedPreferences: a small, persisted piece of state, one per user, that outlives a single interaction.&lt;/p&gt;

&lt;p&gt;And this reveals why mobile leans on tokens in the first place. A browser automatically stores and sends cookies back for you. It is free. A mobile app has no such built in mechanism. So you store the token yourself (in Keystore) and attach it to every outgoing call by hand, which, if you have ever written an OkHttp interceptor that injects an &lt;code&gt;Authorization&lt;/code&gt; header, is a pattern you already know by heart. The interceptor is you doing by hand what the browser does automatically with cookies.&lt;/p&gt;

&lt;p&gt;That single realization, that tokens are what you use when there is no browser to manage cookies for you, explained more than any tutorial had.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real tradeoff: speed and scale versus control and simplicity
&lt;/h2&gt;

&lt;p&gt;Once it is framed as stateless versus stateful, the tradeoff is clean.&lt;/p&gt;

&lt;p&gt;Stateless (for example a plain JWT) verifies with signature math and touches no database. That makes it fast and, more importantly, easy to scale across many servers, because any server in your fleet can verify a token on its own with no shared store to consult. This is why distributed systems, microservices, and serverless functions love it. The cost is real though: because the server keeps no record, you cannot revoke a stateless token before it expires, not without adding server side state, which partly defeats the point. Your only lever is to make it expire quickly.&lt;/p&gt;

&lt;p&gt;Stateful (a session, or a token the server tracks) verifies by looking the credential up in a store the server controls. That gives you instant revocation: kill it immediately, see every active session, log someone out on demand. It is also simpler on the client, since the browser handles the cookie. The cost: every request pays for a lookup, and the moment you run more than one server, you need a shared store (Redis, a database) that all servers can reach. At modest scale this is trivial. At very large or highly distributed scale it becomes a genuine consideration. (To be clear for the experts reading: sessions absolutely can scale with something like Redis. The point is that it is an added dependency and a cost on every request, not that it is impossible.)&lt;/p&gt;

&lt;p&gt;This tradeoff is also exactly why the access token and refresh token pairing exists. The access token is stateless and expires quickly (often about 15 minutes) so it is fast and low risk if stolen, because it dies soon. The refresh token is stateful, lives a long time, is sent rarely, and is stored on the server so it can be revoked, and so it can quietly mint new access tokens without forcing the user to log in every 15 minutes. You get stateless speed where the traffic is constant, and stateful control where you can afford the lookup.&lt;/p&gt;

&lt;p&gt;The recommended hardening here, refresh token rotation with reuse detection, is worth knowing and worth being honest about. Each time a refresh token is used, the server retires it and issues a new one. If a retired token ever shows up again, that reuse is treated as a theft signal and the whole token family is revoked. It is clever, but be precise about what it does: it detects a compromise after the fact and limits the damage. It does not prevent the initial theft, and it relies on the real client eventually colliding with the stolen one. No single mechanism here is a silver bullet, which is really the recurring theme of the whole topic.&lt;/p&gt;

&lt;h2&gt;
  
  
  So which should you use? My stance.
&lt;/h2&gt;

&lt;p&gt;Here is where I will plant a flag, and where I most want your pushback.&lt;/p&gt;

&lt;p&gt;My default is stateful, meaning sessions. For a typical web application, a browser front end (rendered on the server or a single page app) talking to a single backend at ordinary scale, sessions give you simplicity and instant revocation, and the scaling "cost" is largely theoretical at the sizes most apps actually operate at. I would argue that reaching for stateless JWTs "because they scale" is, for a lot of teams, solving a problem they do not have while quietly giving up revocation. This is not a fringe view either. A sizeable and vocal part of the security community argues against using JWTs as your session mechanism for exactly this reason.&lt;/p&gt;

&lt;p&gt;I go stateless when the topology actually demands it. Concretely, I lean toward tokens when any of these are true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There is a native mobile app (no browser to manage cookies automatically).&lt;/li&gt;
&lt;li&gt;The system is microservices or serverless, where a shared session store becomes a bottleneck or an awkward dependency.&lt;/li&gt;
&lt;li&gt;The API is consumed by third parties or many independent clients.&lt;/li&gt;
&lt;li&gt;There is a genuine need for horizontal scale where session lookups on every request across a large fleet actually hurt.&lt;/li&gt;
&lt;li&gt;You are using an identity provider or SSO (OAuth 2.0 or OIDC) that hands you tokens as part of the protocol anyway.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice what decides it: topology and requirements, not the language, and not the word "enterprise." As an aside, this is why Java and Spring apps feel stateful by default (the servlet &lt;code&gt;HttpSession&lt;/code&gt; has been the native path since the 1990s) while modern Spring REST APIs and microservices happily go stateless with JWT. Same rule, different starting defaults.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3miz28pvh5z8d06yd6h8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3miz28pvh5z8d06yd6h8.png" alt=" " width="800" height="1793"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If I had to compress it to one sentence: stateless is a trade you make to buy scale and distribution, so if you are not buying those, do not pay the price.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bigger takeaway
&lt;/h2&gt;

&lt;p&gt;Two things stuck with me beyond the auth details.&lt;/p&gt;

&lt;p&gt;First, good security is layered tradeoffs, not walls. You rarely make an attack impossible. You make the direct attack impractical, then stack defenses that detect and limit the indirect ones. Tokens that expire quickly, rotation, reuse detection, HTTPS, HttpOnly, revocation: each closes a different gap, and none is sufficient alone.&lt;/p&gt;

&lt;p&gt;Second, and this mattered most for me as someone still growing into backend, almost none of this is Node specific. Cookies, sessions, tokens, statelessness, the client and server split: these are properties of the web, not of one language or framework. The syntax and library names change when you move to FastAPI or Spring. The concepts do not. That portability is exactly why deep diving once, from first principles, is worth far more than memorizing one framework's auth recipe.&lt;/p&gt;

&lt;p&gt;That is my understanding, rephrased in my own words. I am sure many of you have scars and strong opinions here, so I will ask directly: in your last project, did you go stateful or stateless, and what actually made the call? I would love to learn where your experience differs from mine.&lt;/p&gt;

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