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

Nextjs - Fixing Loading External Images and Issue of Url Paramater Not Allowed

Nextjs - Fixing Loading External Images and Issue of Url Paramater Not Allowed

Introduction In this post, we will see How to load external images to your next…

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…

Nextjs - How to Build Next.js Application on Docker and Pass API Url

Nextjs - How to Build Next.js Application on Docker and Pass API Url

Introduction In this post we will see: How to prepare a docker image for your…

Nextjs - How to Redirect to Another Page and Pass State

Nextjs - How to Redirect to Another Page and Pass State

Introduction We have a page, for example a . And, upon submission we would want…

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 use Draft.js WYSWYG with Next.js and Strapi Backend, Create and View Article with Image Upload

How to use Draft.js WYSWYG with Next.js and Strapi Backend, Create and View Article with Image Upload

Introduction In this post, we will use in Next.js with strapi. And, we will…

Latest Posts

Claude Code Skills — Build a Better Engineering Workflow with AI-Powered Code Reviews, Security Scans, and More

Claude Code Skills — Build a Better Engineering Workflow with AI-Powered Code Reviews, Security Scans, and More

Most developers use Claude Code like a search engine — ask a question, get an…

Building an AI Voicebot for Visitor Check-In — A Practical Guide to Handling the Messy Parts

Building an AI Voicebot for Visitor Check-In — A Practical Guide to Handling the Messy Parts

Every office lobby has the same problem: a visitor walks in, nobody’s at the…

Server Security Best Practices — Complete Hardening Guide for Production Systems

Server Security Best Practices — Complete Hardening Guide for Production Systems

Every breach post-mortem tells the same story: an unpatched service, a…

Staff Engineer Study Plan for MAANG Interviews — The Complete 12-Week Roadmap

Staff Engineer Study Plan for MAANG Interviews — The Complete 12-Week Roadmap

If you’re a Senior Engineer (L5) preparing for Staff (L6+) roles at MAANG…

XSS and CSRF Explained — The Complete Guide with Real Attack Examples and Defenses

XSS and CSRF Explained — The Complete Guide with Real Attack Examples and Defenses

XSS and CSRF have been in the OWASP Top 10 for over a decade. They’re among the…

OWASP Top 10 (2021) — Every Vulnerability Explained with Code

OWASP Top 10 (2021) — Every Vulnerability Explained with Code

The OWASP Top 10 is the industry standard for web application security risks. If…