DEV Community

Cover image for Implementing Strapi authentication in NextJS
De Oliveira Tristan
De Oliveira Tristan

Posted on

Implementing Strapi authentication in NextJS

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

mkdir examples-nextjs-strapi
yarn create create strapi-app story-api --quickstart
yarn create next-app story-front
Enter fullscreen mode Exit fullscreen mode

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.

yarn add axios next-iron-session next-connect
Enter fullscreen mode Exit fullscreen mode

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.

import axios from 'axios';

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

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:

NEXT_PUBLIC_API_URL=http://localhost:1337
Enter fullscreen mode Exit fullscreen mode

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

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);
}
Enter fullscreen mode Exit fullscreen mode

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

SECRET_COOKIE_PASSWORD=AZERTYUIOP
Enter fullscreen mode Exit fullscreen mode

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:

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);
    }
  });
Enter fullscreen mode Exit fullscreen mode

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:

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

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

{
  "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"
}
Enter fullscreen mode Exit fullscreen mode

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

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;
Enter fullscreen mode Exit fullscreen mode

If you open your browser on 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:

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;
Enter fullscreen mode Exit fullscreen mode

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.

// Code related to the page

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

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:

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;
Enter fullscreen mode Exit fullscreen mode

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.

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();
  });
Enter fullscreen mode Exit fullscreen mode

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

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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

Top comments (9)

Collapse
 
rijast profile image
Rijast

Great article thanks! Do you have a link to the git repo you mentioned? Thanks a lot!

Collapse
 
icrotz profile image
De Oliveira Tristan

Oh yeah, I forgot to add it, it's edited !

Collapse
 
rijast profile image
Rijast

Thanks! I am getting a 404 though...

Thread Thread
 
icrotz profile image
De Oliveira Tristan

Should be good now !

Thread Thread
 
rijast profile image
Rijast

Awesome thanks! 👍

Collapse
 
vickbay profile image
Vick-Bay

Great Article! Had an issue but solved it now. Password has to be at least 32 characters long as per the IronSession documentation :)

Collapse
 
szymon_sus_2b2ce9236c2a66 profile image
Szymon sus

Can you create same example but with qraphql?

Collapse
 
ardies profile image
Ardies Lebon

Thank! Nice

Collapse
 
dsoares profile image
Daniel Soares

Thank you very much for the article. I spent the day trying to make it work with next-auth until I found your text.