javascript|April 18, 2021|3 min read

Authenticating Strapi backend with Next.js and next-auth using credentials and jwt

TL;DR

Authenticate users from Next.js to Strapi using next-auth's CredentialsProvider with email/password, then store and use the JWT token for subsequent authenticated REST API calls.

Authenticating Strapi backend with Next.js and next-auth using credentials and jwt

Introduction

Strapi is a backend system provides basic crud operations with customizable content types, and auto-magically provide its Rest APIs. Next.js is an excellent framework over React.js which uses capability of React.js and provides SEO benefits by rendering pages at server side.

In this post, I will authenticate a registered user fron next.js to strapi and use its Rest API with jwt for further authenticated operations.

What We Will Learn

  • Render a pre-built login form, and its corresponding actions
  • authentication will be with strapi email/password system.
  • Signout functionality
  • Save jwt in session
  • Use jwt tokens in rest-apis for authenticated calls

Pre-requisites

  1. Have a registered and active user in strapi.
  2. Have a content type.
  3. This content type is only usable by authenticated users.

NPM Libraries required

  • axios
  • next
  • next-auth
  • react
  • react-dom

Using Next-auth

Create a file named /pages/api/auth/[...nextauth].js

import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'
import axios from 'axios'

const options = {
  providers: [
    Providers.Credentials({
      name: 'Credentials',
      credentials: {
        email: { label: "Email", type: "text", placeholder: "[email protected]" },
        password: {  label: "Password", type: "password" }
      },
    async authorize(credentials) {
        try {
          const { data } = await axios.post(`${process.env.NEXT_PUBLIC_API_URL}/auth/local`, {
            identifier: credentials.email,
            password: credentials.password
          });
          if (data) {
            return data;
          }
          else {
            return null;
          }
        } catch (e) {
          // console.log('caught error');
          // const errorMessage = e.response.data.message
          // Redirecting to the login page with error message          in the URL
          // throw new Error(errorMessage + '&email=' + credentials.email)
          return null;
        }
      }
    })
  ],

  session: {
    jwt: true,
  },

  callbacks: {
    // Getting the JWT token from API response
    jwt: async (token, user, account) => {
      const isSignIn = user ? true : false;
      if (isSignIn) {
        token.jwt = user.jwt;
        token.id = user.user.id;
        token.name = user.user.username;
        token.email = user.user.email;
      }
      return Promise.resolve(token);
    },
  
    session: async (session, user) => {
      session.jwt = user.jwt;
      session.id = user.id;
      return Promise.resolve(session);
    },
  }
}

export default (req, res) => NextAuth(req, res, options)

Edit _app.js

import { Provider } from 'next-auth/client'

function MyApp({ Component, pageProps }) {
  return (
    <Provider session={pageProps.session}>
        <Component {...pageProps} />
    </Provider>
  );
}

export default MyApp

.env.local Environment variable file

NEXTAUTH_URL=http://localhost:3000
NEXT_PUBLIC_API_URL=http://localhost:1337

Summary so far

We have configured our Next.js frontend so that we will be able to:

  • Configured a credential provider in next-auth with strapi backend.
  • Configured action to be performed when user submit Login form.
  • Authentication rest call to strapi backend and upon successful authentication, save jwt token to session
  • Easily identify if a user has logged-in or not.
  • Easily get jwt token, if user has authenticated.

Playground with Sign-in/Sign-out and Authenticated Rest call

import Head from 'next/head'
import { signIn, signOut, useSession, getSession } from 'next-auth/client'
import axios from 'axios'

export default function Home(initialData) {
  const [ session, loading ] = useSession()
  return (
    <div className='container'>
      <Head>
        <title>Create Next App</title>
        <link rel="icon" href="/favicon.ico" />
        <link rel="stylesheet" href="/style.css"/>
      </Head>

      <h1>Auth Test</h1>

      <div>
          {!session && <>
          Not signed in <br/>
          <button onClick={() => signIn()}>Sign in</button>
        </>}
        {session && <>
          Signed in as {session.user.email} <br/>
          <button onClick={() => signOut()}>Sign out</button>
        </>}
      </div>

      <h1>Content...</h1>

      <div>
        {initialData.journals && initialData.journals.map((each, index) => {
          return(
            <div key={index}>
              <h3>{each.Title}</h3>
              <p>{each.Journal}</p>
            </div>
          )
        })}
      </div>
      
    </div>
  )
}

export async function getServerSideProps({req}) {
  let headers = {}
  const session = await getSession({ req });
  if (session) {
    headers = {Authorization: `Bearer ${session.jwt}`};
  }
  let journals = [];
  try {
    let {data } = await axios.get(`${process.env.NEXT_PUBLIC_API_URL}/journals`, {
      headers: headers,
    })
    journals = data;
  } catch (e) {
    console.log('caught error');
    journals = [];
  }
  
  return {props: {journals: journals}}  
}

Understanding getServerSideProps

First we are checking, if a session is there. If it is, then we are fetching jwt token from session and using it to pass in a Rest call for getting a content type.

Detect User is Logged-in or not

const [ session, loading ] = useSession()

{!session && <>
          Not signed in <br/>
          <button onClick={() => signIn()}>Sign in</button>
        </>}

Also see How to use Next-auth in Client side vs Server Side

The magic methods of next-auth gives you methods to get session information. You just need to check, if session is present.

If there is no session, it gives you a signin action and when you click on this button, you will be presented with the ready-made form.

Related Posts

Request Entity Too Large(413) - Uploading File with Formdata with Axios and Custom header

Request Entity Too Large(413) - Uploading File with Formdata with Axios and Custom header

Introduction I was trying to upload images to my backend using rest APIs. I was…

ReactJS - How to pass method to component and how to pass arguments to method

ReactJS - How to pass method to component and how to pass arguments to method

Introduction In the ReactJS project, you are having aa parent component and a…

ReactJS - Basic Form Handling and Form submission

ReactJS - Basic Form Handling and Form submission

Introduction Lets take a look at how forms are being handled in ReactJS. We will…

How to use Draft.js WYSWYG with Next.js and Strapi Backend, Edit/Update Saved Article

How to use Draft.js WYSWYG with Next.js and Strapi Backend, Edit/Update Saved Article

Introduction This post is in contuation of our previous post: How to use Draft…

How to Integrate Next.js with Strapi Backend and Create a common utility class for REST APIs

How to Integrate Next.js with Strapi Backend and Create a common utility class for REST APIs

Introduction In this post, we will integrate Next.js with Strapi fully. And we…

Tutorial - How to Create a Content-type, and Configure User Permissions for REST APIs

Tutorial - How to Create a Content-type, and Configure User Permissions for REST APIs

Introduction In this post, we will see how we can create a content type. And…

Latest Posts

System Design Patterns for Managing Long-Running Tasks

System Design Patterns for Managing Long-Running Tasks

Introduction Some operations simply can’t finish in the time a user is willing…

System Design Patterns for Real-Time Updates at High Traffic

System Design Patterns for Real-Time Updates at High Traffic

The previous articles in this series covered scaling reads and scaling writes…

System Design Patterns for Handling Large Blobs

System Design Patterns for Handling Large Blobs

Introduction Every non-trivial application eventually needs to handle large…

Explaining SAGA Patterns with Examples

Explaining SAGA Patterns with Examples

In a monolith, placing an order is a single database transaction — deduct…

System Design Patterns for Scaling Writes

System Design Patterns for Scaling Writes

In the companion article on scaling reads, we covered caching, replicas, and…

Serverless vs Containers — The Decision I Keep Revisiting

Serverless vs Containers — The Decision I Keep Revisiting

Every time I start a new service, I have the same argument with myself. Lambda…