Use server-side sessions until JWTs solve a specific problem
For a web app with one backend, server-side sessions make logout and access changes easier to control. Use JWTs when independent verification earns its cost.
A customer asks you to remove a former employee's access. Your application says they have been logged out. Whether their old credential still works is the more useful question.
For a browser application with one backend, the default should be a random session identifier in an HttpOnly cookie, backed by a server-side session record. That recommendation follows from a practical preference: the application should be able to withdraw access where it grants access. A JWT is worth considering when another service needs to verify a claim independently, and the team can explain how stale claims and revocation will work.
Looking back from November 2026, the argument that resurfaced in May 2024 remains a useful design review. Henryk Plötz's post, published on 26 May 2024, argued against using JWTs for ordinary authentication tokens. Its strongest point was about the state that remains after a system calls itself stateless. The recommendation here is narrower than his: independently verified tokens can serve small systems too. Company size is a poor test of whether a trust boundary exists.
Cookie and JWT describe different decisions
A cookie is a browser transport and storage mechanism. A JWT is a claims format. You can put a JWT in a cookie; an opaque session identifier can also travel outside a cookie. Comparing “cookies versus JWTs” hides the decision about where the authoritative record lives.
In the proposed session design, the cookie holds a hard-to-guess identifier. The server uses it to find a session record. In a design based on locally verified signed JWTs, the receiving service verifies the token and applies its acceptance rules. RFC 7519, published in May 2015, defines JWT claims including subject, audience and expiration. A signed JWT does not automatically conceal its contents; encryption is a separate choice.
The important difference is what the service must learn before accepting the next request. If it already needs current customer permissions from a database, independently verifying a token may remove less work than the architecture diagram suggests. That is a reason to examine the design, rather than a claim about measured performance.
Decide what logout promises
Deleting a JWT from one browser does not invalidate a copy that has already escaped. If acceptance depends only on its signature and expiration, that copy remains usable within the acceptance window. Short lifetimes reduce the window. A denylist or a current account-state check can close it earlier, but introduces information the verifier must obtain.
Refresh tokens also need a lifecycle. Revoking a refresh token prevents further renewal; it does not, by itself, teach every offline verifier that an existing access token is withdrawn. RFC 7009, published in August 2013, discusses the implementation choices for revocation, including the difficulties of self-contained tokens and the use of short-lived access tokens. “We have a revocation endpoint” is therefore incomplete evidence for “all access stops immediately.”
A server-side session makes a direct policy easier to express: invalidate the record and reject subsequent lookups. That promise still depends on implementation. A session cache can delay invalidation, and an already authorized request may finish. Ask which event the product promises to stop: the next request, a long-running export, or an existing connection. Those are different obligations with different costs.
Storage deserves its own review
OWASP's HTML5 security guidance advises against keeping session identifiers in localStorage because JavaScript can access them. HttpOnly cookies reduce that particular exposure. They do not make an application immune to cross-site scripting: hostile script may still act through the signed-in browser.
For cookie sessions, use a maintained framework mechanism with Secure and HttpOnly attributes, an appropriate SameSite policy, and protection against cross-site request forgery. Rotate the session identifier after login and relevant privilege changes, and enforce server-side expiry and invalidation. These controls are covered in OWASP's session management guidance. They are current guidance checked for this retrospective, rather than a frozen copy of the documentation from May 2024.
The architectural recommendation is not permission to assemble a login system from isolated snippets. A session store becomes infrastructure to operate. Its availability and consistency matter. Prefer an existing application database when appropriate; add another service only when the requirements justify operating it.
Independent verification can justify the trade
A receiving service may need to verify an identity assertion without contacting the issuer on every request. Organizational boundaries, intermittent connectivity or a deliberate availability requirement can justify that arrangement. The decision should name the required independence and the acceptable delay before a changed permission takes effect.
Once you choose signed tokens, use a maintained implementation and define its validation contract. RFC 8725, published in February 2020, covers algorithm verification, issuer and audience validation, and rules that prevent one kind of JWT being accepted in another context. Key rotation adds another operational question: how do verifiers receive replacement keys, and when do they stop accepting the old ones?
A browser session and tokens between services can coexist. An external identity provider may also participate in login while the application maintains its own browser session. None of these arrangements removes the need to check permissions. Nicole Tietz-Sokolskaya's post of 27 May 2024 makes the useful distinction between login and permissions explicit. A valid identity assertion does not establish a right to read a particular invoice.
Review behavior before replacing the format
During a software takeover, begin with a credential lifecycle review. Establish what happens after logout, account suspension and a permission change. Check whether another browser can replay the old credential. Examine expiration, browser storage and the source of current permissions. For signed tokens, check issuer and audience restrictions and the key rotation procedure.
A system that fails those checks needs work, but changing token format alone is insufficient. Moving JWTs into cookies without fixing invalidation leaves the original promise unresolved. Replacing a working identity integration merely to match a preference can create unnecessary migration risk.
For a conventional application, choose the session mechanism you can explain and revoke. For independent verification, document what remains valid when the issuer is unavailable or access changes. The useful architecture is the one whose behavior matches the promise made to the person withdrawing access.
Paul