Authentication with Next.js and JSON Web Token

Nate Gage
6 min readAug 20, 2020

Concepts covered: Next.js, Node.js, JWT, Cookies, React HOC

Authentication is a challenge, and implementing it from server-side rendered (SSR) applications adds some additional twists!

For this example, we’ll have a login screen that redirects to a home page when a user is authenticated. If you would like to follow along, the file structure and npm dependencies are on the left. This article assumes you are familiar with Express route handling and the axios library.

Take a look at the flowchart below which shows when endpoints in the Node layer are called, when a cookie is set, and when the higher order component is rendered.

Logging in and Generating a JWT

Let’s take a look at a handleSubmit function when a user enters their email and password:

We create a user object that will be added as options to the axios.post request. If res.status comes back 200, we will redirect the user to the home page via window.location = '/home'. If there is an error we display the error message. So how does the user get authenticated in between? As you can see in the flowchart above, the axios request to /signin is first routed to the routes.js file. There are only 2 route handlers for this example:

Inside the signIn function is where we generate a JSON Web Token and set it as a cookie in the browser. The first step is creating another user object to be sent back in the response on a successful login. Since we are not actually fetching a user from a database, I am defaulting the status to 200 so the login will succeed every time.

Below the user object, we create the important first step in our authorization setup — the JSON web token! Install the jsonwebtoken module and require it in the file. We can call the jwt.sign() method and pass it 3 arguments: The first is the payload (our user object), the second is a secret key used to decode/authenticate the token, and the third is an optional expiry date for the token. All of this is information is stored inside a variable called token.

Tip: Since you never want to make the secret key public, as that would defeat the purpose of the token, it is common to store it as an environment variable in a .env file.

Next up is the try/catch statement, in which we use res.cookie() to set our web token as a cookie called userToken. On a successful login the cookie will be set and the user object sent back in the response. With that in place, we can finally move on to the grittier parts of SSR authentication.

Higher Order Components, Middleware, and getInitialProps

At this point, let’s take a step back and look at how Next.js handles our requests after a successful login and the user is redirected to /home.js. There are 2 places for authentication to be implemented here. One is to prevent users from accessing a private page in the browser, and the other is to prevent users from accessing Express endpoints with tools like Postman.

The first case mentioned can be addressed by creating a Higher Order Component that handles authenticating the JSON web token. Every page in your application that you want locked down (like our home page) can be wrapped by this HOC. Pretty efficient, right? The second case of authentication happens with middleware just before the Home component renders. Middleware gives access to the request and response objects and allows for code to be executed in between the request/response cycle. We can create some authentication middleware that will run when the home page makes an initial GET request to /helloUser .

SSR Alert! Both of these cases make use of getInitialProps. This is a Next.js method for injecting props into a component such that the component is rendered with data already populated from the server.

First let’s look at the higher order component, authHOC:

The gist of what RequireAuthentication is doing is returning a React component with getInitialProps, validating the cookie and, if valid, returning the wrapped component’s getInitialProps function and rendering the Home component. Here’s a more detailed explanation…

If there is a cookie called userToken, its value is taken from ctx.req.headers.cookie and stored in the token variable. Then, within the try/catch statement, we can use the jwt.verify() method to verify our session token by passing it the token variable and our secret key. If you’re curious what’s returned from jwt.verify(), it is the user object created in signin.js! The function then returns the child component’s getInitialProps with context. If the token is not verified, isAuthenticated is undefined, which will redirect the user back to the login page.

By wrapping all of our protected pages with this HOC, the user’s session token from cookies is validated before rendering the protected route. Now with a verified token, let’s look at the Home.js page:

The Home component has its own getInitialProps function that makes a GET request to /api/helloUser and returns a greeting prop. Notice after the url, we include an authReqHeader(ctx) function with the request. This function simply takes our userToken cookie and converts it to a conventional authorization token beginning with 'Bearer ‘ :

With that in place, the GET request is routed to the routes.js file. This is where we pass our auth middleware. auth protects our app from that second case of some unauthorized person trying to get access to your Express endpoints with a tool like Postman.

The authentication looks very similar to what was done in the higher order component. Inside the try/catch statement, we create a token variable that holds the value of the bearer token in the request. Once again, jwt.verify() verifies token against the secret key, and stores the value in decoded.

If decoded does contain a user object, we can send decoded.user along in the request headers. If the token cannot be verified, our catch statement will send an error message to the user, and the next() method will not be called.

Tip: Do not forget to call next() at the end of your middleware function, or else the next middleware in line will not run!

With the auth middleware successfully passing, the request to /api/helloUser simply grabs some data off of that user object and sends it back with the greeting prop in the response:

And your Next.js authentication has been implemented! The Home component now has access to the user’s email along with the greeting.

What just happened?

There is a lot going on with JWT authentication. Server rendered applications add an extra layer of complexity since we have to keep in mind what client-side functionality isn’t available to us (like localStorage).

To summarize: Our application was authenticated at 2 places — once in the higher order component, and once in the auth middleware.

  • Upon logging in, a JSON Web Token is created, set as a cookie, and returned in the response along with a user object.
  • When the user is redirected to the home page, they are first authenticated in the higher order component. Once authenticated, the HOC renders the Home component.
  • The Home component uses getInitialProps to retrieve some data via a GET request. This is when the component is authenticated a second time with middleware.

--

--