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
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
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}`,
}
})
}
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
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);
}
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
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);
}
});
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"
}'
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"
}
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;
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;
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,
}
}
})
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;
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();
});
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>
);
};
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)
Great article thanks! Do you have a link to the git repo you mentioned? Thanks a lot!
Oh yeah, I forgot to add it, it's edited !
Thanks! I am getting a 404 though...
Should be good now !
Awesome thanks! π
Great Article! Had an issue but solved it now. Password has to be at least 32 characters long as per the IronSession documentation :)
Can you create same example but with qraphql?
Thank! Nice
Thank you very much for the article. I spent the day trying to make it work with next-auth until I found your text.