Stateless Authentication with Cookies and JWTs

Published 2020-01-11


Photo by Mitchell Orr on Unsplash

Whenever I start building a web app that requires a login, I end up scratching my head on the best way to handle authentication. Should I use server-side sessions? Maybe token auth? Should I keep tokens in cookies or localStorage? Am I going to be prone to cross-site scripting or cross-site request forgery?

In this post, I will outline the way I handle authentication that let's users login in and stay logged in between browser sessions. I generally build the backend as a service so this article will be from that point of view.

EXTREMELY IMPORTANT DISCLOSURE!!! I am not an expert! This is just what I do. Please do more research if you intend on handling sensitive data!

A quick reminder ☝ these posts are meant to be for laymen like myself and are intentionally brief and to the point.

🎁 The Whole Process, Start to Finish

User logs in (over HTTPS!) by POSTing their username and password...

fetch('/api/auth/login', {
  method: 'post',
  body: JSON.stringify(
      username: "Josiah",
      password: "password123"

Assuming that was successful, the server responds with...

204 No content

and sets a JWT in cookies.

The token is based on the user object (username, id, etc. DO NOT include the password) and the cookie has the httpOnly flag set to true. If the client uses some kind of global state, then a boolean such as isAuthenticated can be set to true.

The cookie containing the JWT is now automatically sent along with each subsequent request. The server can use the JWT to verify that the request came from an authenticated caller and/or an authorized caller (if the resource they are requesting is only allowed to permitted users).

When the browser is closed or refreshed and client state is lost (isAuthenticated is false) the token remains in cookies and lets the user attempt to automatically log back in.

When the client app loads (only do this once)...

POST /api/auth/recall

Again, if the user has a cookie it will get sent automatically. The server can check for the existence of a token in the cookies and attempt to verify it is still a good token.

If the cookie token is valid...

204 No content

And the client can again set the state field isAuthenticated to true again, else the user should be directed to login again.

🍪 Cookies

Cookies are key-value pairs given to the client by the server, stored in the browser, and they persist between browser sessions. This is one way to persist a login between page visits and browser sessions.

They usually have some sort of expiration set by the server. If the web app deals with sensitive data (like online banking) the expiration can be as short as 10 minutes. I'll often let cookies live for 30 days if there is no sensitive data.

The most important thing to remember about cookies for token authentication is to set the httpOnly flag to true. This prevents any malicious code on your front-end from retrieving the token stored in cookies.

🥨 The Routes

I chose to have 3 routes for "user entry"

POST /api/auth/signup
POST /api/auth/login
GET /api/auth/recall

signup and login both require a username and password, though signup could require more, such as a re-typed password or anything else that the user account needs on creation.

The recall route is used when attempting to automatically log the user in from cookies. It attempts to recall who the user is. I suppose you could bake this logic into the /login route but I like to keep these routes specific and only let them have one responsibility.

🎟 JWT, the Token

The JWT token is just a JSON object that has been encrypted with some secure method, such as bcrypt.

I'll grab some basic user object (username, id, whatever else) and throw it into an object and use that. Basically, just include whatever user data is going to be needed on subsequent calls when you need to decide if the user has permissions to do whatever it is they try to do next.


There are a plethora of ways to handle web authentication, but this is my approach using JWTs and cookies.