JWT Authentication: Secure by Default
I built Jottings on AWS Lambda, which means stateless functions that scale horizontally and disappear between requests. This created an interesting constraint: I couldn't rely on server-side sessions. Every function invocation needed to be independent.
So I chose JWT authentication, and honestly, it's one of the best technical decisions I made early on.
But here's what surprised me: most developers are either terrified of JWTs or don't understand why they're actually better than the traditional session cookie approach. I want to fix that misconception.
What's Actually Happening With Sessions
Let me start with the old way because understanding the limitation helps explain the solution.
Traditional web apps use sessions. Here's the flow:
User logs in
↓
Server creates session (stores in memory or database)
↓
Server sends back a session ID cookie
↓
User makes request with cookie
↓
Server looks up session in database
↓
"Oh yeah, I remember you. You're logged in."
↓
Server processes request
This works great when you have one server. But scale it to ten servers? Suddenly you need a shared session store (Redis, memcached, database). That adds complexity, latency, and another system to manage.
Now add serverless into the mix. My Lambda functions don't have memory between invocations. They're ephemeral. There's no "one server" to remember you.
Enter JWTs
A JWT (JSON Web Token) flips the model. Instead of the server remembering who you are, you prove who you are on every request.
Here's how it works:
User logs in with Cognito
↓
Cognito creates a token (cryptographically signed)
↓
Token contains user data (user ID, email, etc.)
↓
User sends token with every request
↓
My Lambda function decodes the token
↓
Function verifies the signature (proves it wasn't tampered with)
↓
"This token is valid, I trust the data inside"
↓
Function processes request
No database lookup needed. No session store. No Redis. Just cryptography.
The token is signed with a private key that only Cognito has. My Lambda functions have Cognito's public key, so they can verify the signature without ever talking to Cognito. This verification happens in milliseconds.
Why This Is Actually Secure
I've heard the concerns: "But what if someone gets the token? What if they copy it?"
Here's the thing: that's not how tokens are compromised in practice.
Tokens have expiration times. I set mine to expire after 1 hour. An attacker would need to:
- Intercept the token (in transit, or from storage)
- Use it before it expires
- Impersonate the user within that narrow window
Compare this to session cookies: if someone steals your session ID, it's good until the session expires—often days or weeks.
And here's the subtle security win: stateless authentication is auditable. Every request includes the full proof of who you are (the token). I can log it, decrypt it, inspect it. With sessions, the server has to remember what it said yesterday. If there's a bug in that memory, you've got a problem.
Cognito adds another layer: it supports multi-factor authentication, device tracking, and automatic refresh tokens. Your token expires after 1 hour, but there's a separate refresh token (with longer expiry) that silently gets you a new access token without re-logging in.
The Practical Benefits
For Jottings specifically:
Zero infrastructure overhead: I don't manage a session store. No Redis cluster. No database bloat from expired sessions. Just Lambda + Cognito.
Infinite horizontal scaling: Every Lambda invocation is independent. Add 100 more Lambda instances, no problem. They all verify tokens the same way.
Works across domains: A token from auth.jottings.me is verified the same way at api.jottings.me or dashboard.jottings.me. All I need is Cognito's public key.
Debugging is easier: When someone reports an issue, I can decode their token and see exactly what permissions they had at that moment. Sessions? I'd have to query the session store if it still exists.
Cost efficiency: No session store means no extra monthly bill. Cognito costs are tiny (users are the bottleneck, not tokens).
What About the Token Size?
One legitimate complaint: JWT tokens are bigger than session cookies.
A typical JWT looks like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
That's about 200 bytes. Compare to a session ID like abc123, which is 6 bytes. Session wins on size.
But here's the context: modern HTTP handles this fine. Your request body is kilobytes. A JWT is negligible. And remember, you're not paying for the session store infrastructure, so you're winning on total cost and complexity.
The Honest Tradeoffs
Stateless authentication isn't a silver bullet.
If you need to revoke a token immediately (user account compromised, kick out all sessions), JWTs make this harder. The token is valid until expiration. Cognito helps here with device tracking and browser session management, but it's not instantaneous like session revocation.
And if you're building something where user state changes frequently and needs to be globally consistent, sessions are simpler. But that's rare. Most apps don't need that level of synchronization.
How Jottings Uses This
At Jottings, when you sign up (whether via email, Google, or Apple), Cognito issues you tokens:
- Access token (1 hour expiry) - Proves you're authenticated
- ID token (1 hour expiry) - Contains your profile data (email, name, sign-up method)
- Refresh token (30 day expiry) - Lets you silently get new access tokens
Your browser stores these securely. On every API call, the access token is sent as a header. My Lambda functions decode it, verify the signature, and grant access.
When your access token expires, the frontend uses the refresh token to get a new one, all without you noticing. When your refresh token expires (30 days), you log in again.
It's seamless, secure, and costs me almost nothing to operate.
Why I'm Telling You This
Building Jottings taught me that good security doesn't have to be complicated. JWTs feel mysterious because people talk about them in cryptographic terms, but the actual concept is simple: prove your identity with a signed token.
I also learned that the right architecture choice multiplies over time. Choosing JWTs meant I could scale from 10 users to 10,000 without rewriting authentication. That's worth a lot.
If you're building something serverless—or just tired of managing session stores—JWTs are worth understanding. They're not a trend. They're the natural fit for modern, distributed systems.
If you're curious about how this all works in practice, come try Jottings. You'll see JWT authentication in action every time you log in. And if you want to understand your own authentication better, that's a sign you should be more intentional about how you build it.
Choose boring security. Choose tokens that work at scale. Choose your own authentication story.