Sessions and JWTs are both valid authentication strategies. They solve the same problem differently, and the difference matters for how your system scales, how you revoke access, and how much complexity you carry. This post gives you the comparison so you can pick the right one — not the fashionable one.
How sessions work
On login, the server creates a session record in a store (Postgres, Redis, or even in-memory) and sends the session ID to the client as an HttpOnly cookie. On every request, the client sends the cookie; the server looks up the session ID and retrieves the user.
The server is the source of truth. Revocation is instant: delete the session row and the user is logged out on the next request, no matter how long the cookie is valid for.
How tokens work
On login, the server signs a JWT containing the user ID and sends it to the client. The client stores it and sends it with every request. The server verifies the signature — no database lookup required.
The client holds the truth. Revocation is hard: the token is valid until it expires, unless you maintain a blocklist (which reintroduces a database lookup and mostly negates the statelessness advantage).
The comparison
// Sessions
Revocation: instant (delete the row)
Scalability: requires shared store across instances
DB lookup: every request
Cookie-safe: yes (HttpOnly, SameSite)
Mobile-friendly: workable, but cookies are fiddly
// JWTs
Revocation: not without a blocklist
Scalability: stateless, no shared store
DB lookup: only on refresh
Cookie-safe: store refresh in HttpOnly cookie; access token in memory
Mobile-friendly: yes (Authorization header)When to pick sessions
- You need instant revocation. Compliance requirements, security incidents, account suspension — all require being able to log a user out immediately.
- Single backend service. No coordination overhead for a shared session store.
- Server-rendered app or same-origin SPA.
HttpOnlycookies work cleanly; XSS risk is lower.
When to pick tokens
- Multiple services verifying identity. Each service verifies the signature independently without calling back to a central auth service.
- Mobile apps. Managing cookies in native clients is painful. An Authorization header is simple.
- Third-party API access. Giving a token to a partner API is cleaner than giving them a session cookie.
Implementation in Go
A session store in Go with Postgres is four operations: create, get, delete, and sweep (clean up expired rows).
CREATE TABLE sessions (
id TEXT PRIMARY KEY, -- random, URL-safe
user_id UUID NOT NULL REFERENCES users(id),
data JSONB, -- optional: store extra state
expires_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON sessions (expires_at); -- for efficient sweepsGenerate the session ID with crypto/rand, not math/rand. Set the cookie with HttpOnly, Secure, and SameSite=Lax. Sweep expired sessions with a background goroutine or a cron job — don't let the table grow unbounded.
For JWT authentication in Go specifically, see our JWT deep-dive. If you're building the whole auth layer from scratch, we've done it enough times to go fast.
End of article · Thanks for reading