Context picks the answer: revocability vs statelessness
Here is the trap this lesson disarms: the internet is full of "JWT vs sessions — which should you use?" posts that pick a winner. Neither wins. They are optimised for different contexts, and you have now lived in both.
A mobile client has no cookie jar — and talks to an API. Pica is a native app hitting a JSON API. There is no browser automatically attaching a cookie; the app manages its own credential and sets a header explicitly. The API may be several stateless instances behind a load balancer, with no shared session table. In that world a self-contained, signature-verified token is the natural fit: every instance can check it alone, and the client was going to manage and attach the credential by hand anyway. The JWT's statelessness is a feature here.
A server-rendered app has a browser — and a session table. SignFlow renders HTML to a browser, which automatically sends a cookie with every request. There is already a database in the request path (every page reads from it). Adding one indexed session lookup is negligible, and in return the app gets server-authoritative sessions: it can see who is logged in, expire sessions, and revoke instantly. The session cookie's statefulness is a feature here.
The trade, named: revocability vs statelessness. A JWT gives you stateless verification and takes away revocation. A server session gives you revocation and takes away statelessness (it needs a store). You do not get both for free — every design pays one of these costs. The context decides which cost is cheaper:
| Pica (mobile → API) | SignFlow (browser → server-rendered) | |
|---|---|---|
| Credential | JWT in Authorization header |
opaque token in HttpOnly cookie |
| Server state | none — verify by signature | a sessions row per login |
| Verification | recompute the signature | DB lookup by token hash |
| Revocation | impossible (nothing to delete) | one DELETE |
| Sent by | the client, explicitly | the browser, automatically |
| Scales to N instances | trivially (stateless) | needs a shared session store |
Read the table as one sentence: the answer changes because the client and the deployment change. This is not a matter of taste or fashion; it is the mechanism matching its environment. You can argue it from experience now, because you shipped both.