Implementing Strapi authentication in NextJS
Tags: nextjs, strapi, javascript, react
Published: 2021-05-21 09:21:34

In one of our previous projects, we decided to use Strapi to gain time in development. It's a great Headless CMS built with NodeJS and ReactJS providing either a Rest and a GraphQL API. We used NextJS for the front application and we choose to use the default authentication method provided by Strapi.
Firstly, we found an article on the Strapi blog explaining exactly our case and we were super happy about that. But when decided to start the implemented we discovered that the security of it was not so perfect because they use a plain cookie to keep to the token.
After some research, we tried to use a library called [next-auth](https://next-auth.js.org/) with the `Credentials` provider included by default. The authentication was working but the front application was not able to receive the Strapi JWT Token required to perform the request on the rest API. We found that next-auth was more oriented for applications using OAuth authentication than a basic email and password system.
Because of this we moved to [next-iron-session](https://github.com/vvo/next-iron-session). And after some magic, everything was working like a charm.
In this article, we gonna see how you can achieve the same thing in less than 20 minutes.
# Setup the project
First, we need to set up our project by creating a NextJS and a Strapi application. I suggest you check the documentation of each project for this but here is what I did:

```bash
mkdir examples-nextjs-strapi
yarn create create strapi-app story-api --quickstart
yarn create next-app story-front
```

You will have to wait for all the dependencies to install. When it's done you can start the Strapi application and create your admin account. So you will be able to create the first account from the admin panel.
# Configure the authentication
Now we are going to set up the NextJS project to use the Strapi API. First, we need to install `next-iron-session` to manage the authenticated sessions, `axios` for the HTTP requests (You can choose to use fetch instead but I prefer the axios API) and `next-connect` to handle middlewares.

```plain
yarn add axios next-iron-session next-connect
```

When it's done we can start creating the utils we will need in our routes. You can create a file named `utils/strapi.js` and fill it with the following code. We will use this util in the API to communicate with Strapi.

```javascript
import axios from 'axios';

export function createStrapiAxios(user) {
  return axios.create({
    baseURL: process.env.NEXT_PUBLIC_API_URL,
    headers: user && {
      Authorization: `Bearer ${user?.strapiToken}`,
    }
  })
}
```

You can see that we are creating a function that provides a custom axios instance filled with the URL of the Strapi application and the authentication header when a user is present. To make `NEXT_PUBLIC_API_URL` available you need to add it to your environment. Create a file named `.env.local` add this to it:

```plain
NEXT_PUBLIC_API_URL=http://localhost:1337
```

After that we need to create the session middleware. Create a file named `middlewares/session.js` and fill it with the following code.

```javascript
import { withIronSession, ironSession } from 'next-iron-session';

const sessionConfig = {
  password: process.env.SECRET_COOKIE_PASSWORD,
  cookieName: 'next-session',
  cookieOptions: {
    secure: false,
  },
};

export const sessionMiddleware = ironSession(sessionConfig);

export function withSession(handler) {
  return withIronSession(handler, sessionConfig);
}
```

The middleware will be used in api routes to handle add the session to the request and the `withSession` will be used in pages to access the session from the `getServerSideProps` function. To make it work you also need to add this line to your `.env.local`

```javascript
SECRET_COOKIE_PASSWORD=AZERTYUIOP
```

This is the secret used to encrypt the session on the client side.
When it’s done we can start creating a route to handle authentication. Create a file named `pages/api/login.js` and fill it with the following code:

```javascript
import nc from 'next-connect';
import { sessionMiddleware } from '../../middlewares/session';
import { createStrapiAxios } from '../../utils/strapi';

export default nc()
  .use(sessionMiddleware)
  .post(async (req, res) => {
    const { email, password } = req.body;

    try {
      const user = await createStrapiAxios()
        .post(`/auth/local`, {
          identifier: email,
          password,
        })
        .then((res) => res.data)
        .then((data) => ({
          ...data.user,
          strapiToken: data.jwt,
        }));
      
      if (!user.confirmed) {
        return res.status(401).json({
          statusCode: 401,
          message: 'User not confirmed'
        });
      }

      req.session.set('user', user);
      await req.session.save();
      res.json(user);
    } catch (error) {
      const { response: fetchResponse } = error;
      if (fetchResponse) {
        return res.status(fetchResponse?.status || 500).json(error.response?.data);
      }
      res.status(500).json(error);
    }
  });
```

We are using `next-connect` to use the `sessionMiddleware` and force the usage of the `POST` method. We take the `email` and the `password` from the body and use them to authenticate the user on the Strapi API. If the authentication succeeds we store the user data in the session with the generated jwt for later use and return the user as the response.
You can try this route using the following curl command:

```bash
curl --location --request POST 'http://localhost:3000/api/login' \
--header 'Content-Type: application/json' \
--data-raw '{
    "email": "test@test.fr",
    "password": "Testtest123"
}'
```

If everything is correctly setup you will receive something like this as a result:

```json
{
  "id": 1,
  "username": "test",
  "email": "test@test.fr",
  "provider": "local",
  "confirmed": true,
  "blocked": false,
  "role": {
    "id": 1,
    "name": "Authenticated",
    "description": "Default role given to authenticated user.",
    "type": "authenticated"
  },
  "created_at": "2021-05-19T14:48:06.065Z",
  "updated_at": "2021-05-19T15:54:35.556Z",
  "strapiToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNjIxNDQ1NzM0LCJleHAiOjE2MjQwMzc3MzR9.1jIJAl_GotO5UWyIsPU5kCeba44ZIhJOiNiuqjyIFGw"
}
```

# Add the login page
Now we're ready to add our login page on the NextJS application. Start by creating a new page named login creating a file `pages/login.js`

```jsx
import React from 'react'

const LoginPage = () => {
  return (
    <div>
      <h1>Login to your account</h1>
      <form method="post" action="/api/login">
        <label htmlFor="email">Email</label>
        <input type="email" name="email" placeholder="test@test.fr" />
        <label htmlFor="password">Password</label>
        <input type="password" name="password" placeholder="********" />
        <button type="submit">Submit</button>
      </form>
    </div>
  )
}

export default LoginPage;
```


If you open your browser on [http://localhost:3000/login](http://localhost:3000/login) you will see the form. Try filling the field with correct information, you will be redirected on the API with the user data as a response. But you will stay on that page, and it's not the best experience. So let's add some react magic:

```jsx
import React from 'react'
import { useRouter } from 'next/router';
import axios from 'axios';

const LoginPage = () => {
  const router = useRouter();

  const onSubmit = (event) => {
    event.preventDefault();

    const body = {
      email: event.currentTarget.email.value,
      password: event.currentTarget.password.value,
    };

    axios.post('/api/login', body).then((user) => {
      console.log(user);
      router.push('/');
    });
  }
  return (
    <div>
      <h1>Login to your account</h1>
      <form method="post" action="/api/login" onSubmit={onSubmit}>
        <label htmlFor="email">Email</label>
        <input type="email" name="email" placeholder="test@test.fr" />
        <label htmlFor="password">Password</label>
        <input type="password" name="password" placeholder="********" />
        <button type="submit">Submit</button>
      </form>
    </div>
  )
}

export default LoginPage;
```

# Display current user on the home page

Okay, now when the authentication succeeds the user data is added to your session, and you are redirected to the home page. Let's try displaying the currently authenticated user on the home page.
You have multiple ways of doing this, first one is to create a new API route exposing the user and request it from the front end. The second one is to use the getServerSideProps to return the user on each page. Let's use the second method because it's simplier.
If you remember we created a function named `withSession`. Let's use it in our home page (file named `pages/index.js`) to provide the data as props to the page.


```jsx
// Code related to the page

export const getServerSideProps = withSession((context) => {
  const { req } = context;
  return {
    props: {
      user: req.session.get('user') || null,
    }
  }
})
```

We use the `withSession` to inject the session in the request object located in the context. We can access the currently authenticated user using the `req.session.get`, if no user is present `undefined` is returned. NextJs doesn't like when undefined is passed as server props so we have to use `null` instead.
From now the page have access to the authenticated user in its props. Let's render some informations:

```jsx
import Link from 'next/link';
import { withSession } from '../middlewares/session';

const HomePage = (props) => {
  const { user } = props;

  return (
    <div>
      <h1>Home</h1>
      {user ? (
        <p>Hello {user.username}, have a nice day !</p>
      ) : (
        <p>
          Hello guest, maybe you want to{' '}
          <Link href="/login">
            <a>login ?</a>
          </Link>
        </p>
      )}
    </div>
  );
};

export const getServerSideProps = withSession((context) => {
  const { req } = context;
  return {
    props: {
      user: req.session.get('user') || null,
    }
  }
})

export default HomePage;
```

# Implement logout

The last thing we need to do is implementing logout. To do that we will create a new file named `pages/api/logout.js`. This route will be responsible for destroying the session.


```javascript
import nc from 'next-connect';
import { sessionMiddleware } from '../../middlewares/session';
import { createStrapiAxios } from '../../utils/strapi';

export default nc()
  .use(sessionMiddleware)
  .post(async (req, res) => {
    req.session.destroy();
    res.send();
  });
```

Now we can add the logic in our page to call this route:

```jsx
import axios from 'axios';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { withSession } from '../middlewares/session';

const HomePage = (props) => {
  const { user } = props;
  const router = useRouter();

  const onLogout = (e) => {
    e.preventDefault();
    axios.post('/api/logout').then(() => {
      router.push('/login')
    })
  }

  return (
    <div>
      <h1>Home</h1>
      {user ? (
        <p>Hello {user.username}, have a nice day ! Maybe you want to <a href="/api/logout" onClick={onLogout}>logout ?</a></p>
      ) : (
        <p>
          Hello guest, maybe you want to{' '}
          <Link href="/login">
            <a>login ?</a>
          </Link>
        </p>
      )}
    </div>
  );
};
```

Now if you click on the logout button you will be logged out and redirected to the login page.

# Conclusion

And that's it. Now you have a NextJS application using Strapi for the authentication. Other methods exist to do the same job, for example doing the authentication directly with Strapi and only save the JWT in the NextJS session but this method was easier for us and easy to explain. You can find the full source code of what we did on our GitHub. I also included an example on how to access the user from the API: to return the currently authenticated user as JSON.
If you have any questions feel free to post a comment and if this story helped you don't forget to like and share.

Good luck with your project!

Github Link: https://github.com/KOJI-SAS/examples-nextjs-strapi