Next.js HTTP Authentication with JWT and cookies
Adding authentication to a web project
I was recently trying to figure out a way to implement simple HTTP authentication for a personal Next.js project. This is when I came across HTTP authorization using Next.js
This is the code for basic HTTP authentication using
This code servers as an example only and is not production-ready: the username and password (basic and insecure as they are) are not hashed and it lacks rate limiting, location tracking, CSRF and a plethora of other security measures. Copy and paste at your own risk!
However, an authentication header only persists within the browser session, meaning that one has to re-authenticate on every new session. I instantly thought of keeping a token within cookies, however there are multiple types of cookies:
- Cookies with a
- Cookies with a
(Information retrieved from developer.mozilla.org).
The goal is to build a framework using
HttpOnly cookies for ideal security.
Another hurdle I came across was hashing. You obviously don't want to store your user's email and password as plain text in cookies. That's where JWT (JSON Web Tokens) come in. An industry standard for storing and transmitting key-value data securely across the web, it is one of the safest ways to store user authentication data in cookies, so that one doesn't have to log in every time!
What is a JWT?
Basically, it is a string, for example
aaaaaa.bbbbb.cccc. It has three parts, each delimited by a period. The first part is the header. It contains the type of token (JWT) and the signing algorithm used. The second part is the payload. This is where your data is stored. The last part is a signature, where the encoded header and payload are signed, with a secret. This is used to make sure that data hasn't been tampered with along the way. The secret, as the name suggests, is only known by the person creating the token and makes sure that no one else can sign the token.
There is a difference between encrypting and signing: Encrypting is preventing other people from being able to read data, where is signing is verifying authenticity of the data, with third parties still being able to read it.
Now back to our user authentication example. Here is the idea:
- The user authenticates with their username and password,
- If the username and password are recognized, then access is granted, and a hash of the user-password pair is created.
- The hash is stored into a JWT, with a secret known only to you. The JWT is stored in a cookie, with a predefined expiration date.
- Upon each subsequent visit, the server checks if the cookie exists, and verifies it by decoding the JWT and checking the hash. If it's valid, access is granted, otherwise the user is presented the authentication page.
Thanks to help from Vercel's JWT authentication example, which I adapted to my use case, i.e. a multi-user authentication web app, I built a few helper functions:
verifyAuth which checks if the user has a cookie set and if it matches a user in the database;
setUserCookie which sets a JWT cookie for the provided user upon successful authentication,
expireUserCookie which causes a cookie to expire in case we want to log out the user.
Here are the functions:
With the helper functions implemented, it's a matter of combining them with the basic
next-js-middleware HTTP authentication.