Doğacan Bilgili A software developer who is also into 3D-modeling and animation.

Implementing user registration and authentication with Strapi and Next.js

7 min read 2132

user registration and authentication with strapi and nextjs

This article will walk you through using Strapi CMS with Next.js to create an application where you can register and authenticate users and persist sessions.

Setting up the Next.js app

Let’s start with setting up the Next.js application. The easiest way to set this up is to use the create-next-app package.

In your terminal, run npx create-next-app next-app, where next-app is any name you prefer for your project.

This will generate a folder named next-app. Then, navigate into that folder in your terminal screen and run npm run dev. You will see the application running on localhost port 3000.

Setting up Strapi

Just like Next.js, Strapi also has an npm package that makes it easy to start a project.

Run npx create-strapi-app strapi-app to create a Strapi project. When prompted, choose the Quickstart option.

Once the installation is done, navigate to the strapi-app folder and run npm run dev. When you visit localhost:1337, you will be asked to create an administrator account. After doing so, you should be directed to Strapi control panel.

Registering a user in Strapi

In the sidebar of the Strapi under Collection Types, you should see Users, which is created by default. This is where you can manually create users and see all of the existing users.

By default, Strapi’s API provides endpoints to create, fetch, and authenticate users.

/auth/local/register is the end-point where we need to send the username, email, and password in order to register a user.

We made a custom demo for .
No really. Click here to check it out.

For this example app, we will have /register and /profile routes, where you can register a user and then see the profile of a user, respectively. The login screen will be on the homepage.

Let’s start with the <RegisterComponent/> component.

This component returns a very basic form, where the user enters their username, email, and password.

Then userData object holds this information and sends it to the /api/register endpoint, which is something we will create within the backend part of the Next application. Note that to make the API call, I use the axios package, so make sure you installed it as a dependency.

npm install --save axios

Once the request made to /api/register is successful, we route the application to /profile in order to display the user information. We will create the /profile page later.

The following file is /components/registerComponent.jsx, and we will use it in /pages/register.jsx:

import { useState } from 'react';
import { useRouter } from 'next/router';
import axios from 'axios';

const RegisterComponent = () => {
  const router = useRouter();
  const [userData, setUserData] = useState({
    username: '',
    email: '',
    password: '',
  })

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      await axios.post('/api/register', userData);
      router.replace('/profile');
    } catch (err) {
      console.log(err.response.data);
    }
  }

  const handleChange = (e) => {
    const { name, value } = e.target;
    setUserData({...userData, [name]: value });
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Username:
        <input type="text" name="username" onChange={e => handleChange(e)} />
      </label>
      <br />
      <label>
        Email:
        <input type="text" name="email" onChange={e => handleChange(e)} />
      </label>
      <br />
      <label>
        Password:
        <input type="password" name="password" onChange={e => handleChange(e)} />
      </label>
      <br />
      <button>Register</button>
    </form>
  )
}

export default RegisterComponent;

Now create register.jsx under the pages folder and just return the <RegisterComponent/>.

import RegisterComponent from "../components/registerComponent";
const Register = () => (
  <RegisterComponent />
)
export default Register;

The next thing we need to do is create the /api/register endpoint.

Under /pages, you will find the /api folder. In that folder, create register.js. That way, when we make a request to /api/register in the Next.js client, it will be handled by this file.

import axios from 'axios';
import { setCookie } from 'nookies'

export default async (req, res) => {
  const { username, password, email } = req.body;

  try {
    const response = await axios.post('http://localhost:1337/auth/local/register', {
      username,
      email,
      password,
    })

    setCookie({ res }, 'jwt', response.data.jwt, {
      httpOnly: true,
      secure: process.env.NODE_ENV !== 'development',
      maxAge: 30 * 24 * 60 * 60,
      path: '/',
    });

    res.status(200).end();
  } catch (e) {
    res.status(400).send(e.response.data.message[0].messages[0]);
  }
}

As mentioned before, the endpoint for registering a user is /auth/local/register. Because Strapi is running on localhost:1337, we send a request to http://localhost:1337/auth/local/register, along with the username, email, and password data that we retrieved from the request body.

Note that if you deploy your Strapi application to a remote server, then you need to replace the base URL accordingly. It’s good practice to store the base URL in an environment variable and then use it instead of hardcoding the URL. However, for the sake of simplicity, I will just use localhost:1337 throughout this article.

The response returned from the POST request contains a JWT token. This token is specific to that user and should be stored securely in order to automatically authenticate the user.

The nookies package is a collection of helper functions that handle cookies in a Next.js application.

The setCookie function takes the response object as the first argument. The second argument is the name of the cookie, and this can be anything you choose.

The third argument is the value of the cookie. In our case, this is the JWT token returned from the request response, and, as the last argument, we can pass an object with options.

The httpOnly flag prevents the client — the javascript running on the browser — from accessing the cookie so that you can protect the cookie from possible cross-site scripting (XSS) attacks.

The secure flag ensures that the cookie is only transmitted over a secure https connection. Because the localhost is not an https connection, we set it to false if the environment we are running the application is development.

maxAge determines how many seconds the cookie will be valid. In this example, it is set to 30 days.

path determines in which path the cookie should be valid. It is set to / in order to make the cookie available for all the paths.

Note that we are sending a message in case of a failed request. This message retrieved from e.response.data.message[0].messages[0] contains useful information regarding why the request failed. This might be an invalid email, or a username already in use, etc.

So, this information can be used to show the appropriate error message in case of a failed registration.

For the sake of simplicity, we don’t handle this error message in the client. We simply log it to show in the browser console instead.

Creating the user profile page in Strapi

This is the /profile route, where the user data, such as username and email, is displayed. This route can be directly visited, or, the user could be routed there when logged in or signed up.

Because this page needs to use the cookie in order to authenticate the user, and because we have set the cookie on the server side with the httpsOnly flag, we need to read the cookie in the server as well.

getServerSideProps is a Next.js function that runs on the server on each request. In this function, we can parse the cookie in order to access the JWT token and then make a request to Strapi to fetch the user data. Then we can return that data from the function, which exposes it as props to the page.

Content of /pages/profile.jsx is as follows:

import { useRouter } from 'next/router';
import axios from 'axios';
import nookies from 'nookies';

const Profile = (props) => {
  const router = useRouter();
  const { user: { email, username } } = props;

  const logout = async () => {
    try {
      await axios.get('/api/logout');
      router.push('/');
    } catch (e) {
      console.log(e);
    }
  }

  return (
    <div>
      <div>Username: {username}</div>
      <div>Email: {email}</div>
      <button onClick={logout}>Logout</button>
    </div>
  )
}

export const getServerSideProps = async (ctx) => {
  const cookies = nookies.get(ctx)
  let user = null;

  if (cookies?.jwt) {
    try {
      const { data } = await axios.get('http://localhost:1337/users/me', {
        headers: {
          Authorization:
            `Bearer ${cookies.jwt}`,
          },
      });
      user = data;
    } catch (e) {
      console.log(e);
    }
  }

  if (!user) {
    return {
      redirect: {
        permanent: false,
        destination: '/'
      }
    }
  }

  return {
    props: {
      user
    }
  }
}

export default Profile;

Let’s focus on the getServerSideProps function first. We again use the nookies package to get the cookies from the context object (ctx). The name of the cookie we set was jwt, hence the check for existence of cookies.jwt.

If this specific cookie is there, then we send a request to the /users/me endpoint of the local Strapi server with the Authorization header containing the JWT token to fetch the user information.

If the cookie does not exist or the JWT token is invalid, the user variable will stay null and we redirect the page back to /, which is the home page with the login screen. Otherwise, we return user in the props object, which then becomes available as a prop in the Profile function we export.

Profile function returns a very basic markup where the username and email are displayed, along with a log-out button, which calls the logout function.

This function sends a request to the /api/logout endpoint in order to delete the cookie and then route the page to /.

Below is /pages/api/logout.js:

import { destroyCookie } from 'nookies'

export default async (req, res) => {
  destroyCookie({ res }, 'jwt', {
    path: '/',
  });

  res.status(200).end();
}

Logging in users

Here’s the content of /pages/index.jsx, which is the homepage and where the <LoginComponent> lies along with a Register button, which routes the page to /register route.

import { useRouter } from 'next/router';
import axios from 'axios';
import nookies from 'nookies';
import LoginComponent from '../components/loginComponent';

const Home = () => {
  const router = useRouter();
  const goToRegister = () => {
    router.push('/register');
  }

  return (
    <div>
      <LoginComponent />
      <button onClick={goToRegister}>Register</button>
    </div>
  )
}

export const getServerSideProps = async (ctx) => {
  const cookies = nookies.get(ctx)
  let user = null;

  if (cookies?.jwt) {
    try {
      const { data } = await axios.get('http://localhost:1337/users/me', {
        headers: {
          Authorization:
            `Bearer ${cookies.jwt}`,
          },
      });
      user = data;
    } catch (e) {
      console.log(e);
    }
  }

  if (user) {
    return {
      redirect: {
        permanent: false,
        destination: '/profile'
      }
    }
  }

  return {
    props: {}
  }
}

export default Home;

As we did in /pages/profile.jsx, we again use the getServerSideProps function to fetch the cookie, and then check the existence of the user.

If the user does exist, then we redirect to /profile route. If not, we return an empty props object and stay on the same route to render the <LoginComponent/>, which has the following content:

import { useState } from 'react';
import { useRouter } from 'next/router';
import axios from 'axios';

const LoginComponent = () => {
  const router = useRouter();
  const [userData, setUserData] = useState({
    identifier: '',
    password: '',
  });

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      await axios.post('/api/login', { ...userData });
      router.push('/profile');
    } catch (err) {
      console.log(err.response.data);
    }
  }

  const handleChange = (e) => {
    const { name, value } = e.target;
    setUserData({...userData, [name]: value })
  }

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <label>
          Email:
          <input type="text" name="identifier" onChange={e => handleChange(e)} />
        </label>
        <br />
        <label>
          Password:
          <input type="password" name="password" onChange={e => handleChange(e)} />
        </label>
        <br />
        <button>Login</button>
      </form>
    </div>
  )
}

export default LoginComponent;

This component renders a form to get the email and password. When the form is submitted, a POST request to /api/login is made and the page is redirected to /profile route.

Below you can see the content of /api/login.js

import axios from 'axios';
import { setCookie } from 'nookies'

export default async (req, res) => {
  const { password, identifier } = req.body;

  try {
    const postRes = await axios.post('http://localhost:1337/auth/local', {
      identifier,
      password,
    })

    setCookie({ res }, 'jwt', postRes.data.jwt, {
      httpOnly: true,
      secure: process.env.NODE_ENV !== 'development',
      maxAge: 30 * 24 * 60 * 60,
      path: '/',
    });

    res.status(200).end();
  } catch (e) {
    res.status(400).send(e.response.data.message[0].messages[0]);
  }
}

/auth/local is a Strapi endpoint to log a user in. The object sent to this endpoint should have the identifier and password keys.

As we did in /api/register.js, we also set a cookie with the same name and options to persist the user session.

Conclusion

In this article, we demonstrated how to use Next.js as a full-stack application to create a user interface with multiple routes and create API endpoints to communicate with Strapi’s API in order to register and fetch user information. Setting and fetching cookies on the server-side within Next.js is also demonstrated in order to persist user sessions.

LogRocket: Full visibility into production Next.js apps

Debugging Next applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web apps, recording literally everything that happens on your Next app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your Next.js apps — .

Doğacan Bilgili A software developer who is also into 3D-modeling and animation.

Leave a Reply