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

April 18, 2021

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.


Similar Posts

Latest Posts