Next.js HTTP Authentication with JWT and cookies

Table of contents

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 middleware.

The webpage you are trying to navigate to displays a dialog before granting access

This is the code for basic HTTP authentication using next-js-middleware:

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:

  • Normal cookies are accessible by client-side javascript, making them prone to security issues, for example a malicious browsing extension retrieving login data (this is called XSS).
  • Cookies with a HttpOnly flag are inaccessible by client-side javascript, i.e. they are only sent to the server which makes them more secure as they can only be modified by the server and the user.
  • Cookies with a Secure flag are only sent during HTTPS requests as well as localhost (handy for development), meaning they will not be visible to the server with HTTP. However they are still visible to client-side javascript if there is no HttpOnly flag set.

(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.

An example of encoding and decoding a JWT, courtesy of jwt.io

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.

All done!

Felix

Felix

Last edited:

F

More posts

Cover Image for Making the Internet More Human

Making the Internet More Human

Navigating the internet has become difficult with all the accessibility issues, pop-ups, cookie banners and advertisements. We’ll explore different ways to make the web a more welcoming place for humans.

Cover Image for Designing spring animations for the web

Designing spring animations for the web

Designing intuitive animations can be tricky. We’ll explore movement in the physical world and see how to replicate this digitally, as well as best practices for responsive animation.