Strapi
Authentication with Google NextAuth and Strapi

Integrating Google, Strapi NextJS with Next-Auth Login

Tested and worked in Strapi 5

For this I did not use any plugins or backend customization, except the mandatory configuration in admin page as in Strapi DOCs - User Permissions: https://docs.strapi.io/dev-docs/plugins/users-permissions#jwt-configuration (opens in a new tab)

Must create credentials in your chosen provider, for ex google: Google API Credentials: https://console.cloud.google.com/apis/credentials (opens in a new tab)

Provider configuration Strapi Provider Configuration

./lib/auth.ts
import type { NextAuthOptions, DefaultSession } from 'next-auth';
import CredentialsProvider from "next-auth/providers/credentials"; // Local provider
import GoogleProvider from "next-auth/providers/google";
// import Facebook from "next-auth/providers/facebook";
 
 
declare module "next-auth" {
  interface Session {
    user: {
      jwt: string;
    } & DefaultSession["user"]
  }
  
  interface User {
    password?: string;
  }
}
 
const api_url = process.env.NEXT_PUBLIC_API_URL;
const token = process.env.STRAPI_TOKEN; // you configure @ {STRAPI_URL}/admin/settings/api-tokens, check: https://docs.strapi.io/user-docs/settings/API-tokens
 
export const authConfig: NextAuthOptions = {
  secret: process.env.NEXT_AUTH_SECRET, // Generate in git terminal: 'openssl rand -base64 32' or copy from https://generate-secret.vercel.app/32
 
  providers: [    
    GoogleProvider({
        // Must configure both at: https://console.cloud.google.com/apis/credentials
      clientId: process.env.GOOGLE_CLIENT_ID as string, // Calling .env file in frontend (You must insert same in Strapi Provider Page)
      clientSecret: process.env.GOOGLE_CLIENT_SECRET as string, // Calling .env file in frontend (You must insert same in Strapi Provider Page)
    }),
    // ENABLED WHEN FACEBOK APROVE
    // Facebook({
    //   clientId: process.env.AUTH_FACEBOOK_ID as string,
    //   clientSecret: process.env.AUTH_FACEBOOK_SECRET as string,
    // }),
    CredentialsProvider({
      // The name to display on the sign in form (e.g. "Sign in with...")
      name: "Email",
      // `credentials` is used to generate a form on the sign in page.
      // You can specify which fields should be submitted, by adding keys to the `credentials` object.
      // e.g. domain, username, password, 2FA token, etc.
      // You can pass any HTML attribute to the <input> tag through the object.
      credentials: {
        email: { label: "Email", type: "text", placeholder: "Email" },
        password: { label: "Senha", type: "password" }
      },
      async authorize(credentials, req) { // Ask authorization from some provider
        if (credentials) { // Credentials from provider
          try {
            /* ONLY LOCAL SERVER LOGIN */
            const url = `${api_url}/api/auth/local`;
 
            const user = {
                identifier: email, // On Post request use 'indentifier' property, strapi will respond with 'email' property
                password: credentials.password
            }
 
            const response = await fetch(url, {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                    "Authorization": `Bearer ${token}` // You must create strapi token
                },
                body: JSON.stringify(user)
            });
 
            const data = await response.json();
 
            if ("user" in data) {
              const { jwt, user } = data;
        
              if (!user.email || !user.id) {
                console.error("Invalid user data:", user.email);
                return null;
              }
        
              return {
                jwt, // Modifying object to put JWT in first level
                id: `${user.id}`,
                email: user.email,
                password: credentials.password
              };
            }
        
            console.error("Invalid response in authorize ", response);
            return null; // Login Error
          } catch (error) {
            console.log("Credentials authorize error", error);
            return null; // Network Error
          }
        }
 
        return null; // If here all have failed
      }
    }),
  ],
 
  callbacks: { // Function to run after authorization
    async signIn({ user, account }) {
      try {
        const { email } = user;
        
        // Basic check to passs
        if (!email) {
          console.error("Incomplete data: Email or Password missing.");
          return false;
        }
 
        if (account?.provider && user.email) {
          return true;
        }
 
        // Case using local Provider, go straight to sign in
        if(!("password" in user)){
          return false;
        } else {
            /* Local Server Login */
            
          const password = user.password as string; // Password from default next-auth button or custom login page
 
            const user = {
                identifier: email, 
                password
            }
 
            const url = `${api_url}/api/auth/local`;
 
            const response = await fetch(url, {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                },
            });
 
        const data = await response.json();
  
          if("user" in data && data?.user){
            return true;
          }
        }
 
        return false;
      } catch (error) {
        console.error("SignIn Error:", error instanceof Error ? error.message : error);
        return false;
      }
    },
    
    async jwt({ token, user, account }) { // Function when it has JWT
        /* Provider Time: Google|Facebook|Github */
 
      if (account?.access_token) {
          const url = `${api_url}/api/auth/${account.access_token}/callback?access_token=${account?.access_token}`; // Confirm if frontend credentials match backend
 
        /* This URL is Always GET method, but it can LOGIN or CREATE the user in database returning data + jwt */
        const response = await fetch(url, {
            method: "GET",
            headers: {
                "Content-Type": "application/json",
            },
        });
 
        const data = await response.json();
 
        if ("jwt" in data) {
          account.access_token = data.jwt; // Reaplace to Strapi token
        }
      }
 
 
      return {
        // My preference adjustment as I prefer in my application
        ...token,
        ...user,
        ...account,
        jwt: account?.access_token || token.jwt, // account?.access_token = External Server jwt
      };
    },
 
    async session({ session, token,  }) {
      const { password, ...tokenNoPassowrd } = token; // Removing password
 
      return {
        // Customizing object again as I need
        ...session,
        user: {
          jwt: token.access_token,
          ...tokenNoPassowrd,
        },
      }
    },
  },
 
  pages: {
    // Only if want to replace default next-auth Login Page
    // Can use default App structure: ./src/app/account/page.tsx I custom to ./src/app/(pages)/account/page.tsx
    signIn: "/account/login",
  },
} satisfies NextAuthOptions;
 

One more file to replace the route.

.src/app/api/[...nextauth]/route.ts
import { authConfig } from "@/app/lib/auth"; // Importing above file auth.ts
import NextAuth from "next-auth/next";
 
const handler = NextAuth(authConfig);
 
export { handler as GET, handler as POST }; // I did not need to adjust for PUT method for example

Provide to all routes

./src/app/layout.tsx
 
import './globals.css';
import Header from "./components/Header"
import Footer from "./components/Footer"
import { NextAuthProvider } from "./providers/nextAuthProvider" // Important
 
 
export default async function RootLayout({children}: {children: React.ReactNode}) {
  return (
    <html lang="pt">
      <NextAuthProvider> {/*Important*/}
        <head></head>
        <body>
          <Header />
          <main className="flex min-h-screen flex-col my-4">
              {children}
          </main>
          <Footer />
        </body>
      </NextAuthProvider>
    </html>
  )
}
./src/app/user/page.tsx
"use client" // Mandatory due to useSession, to convert to server remove this line and use getServerSession, see: https://next-auth.js.org/configuration/nextjs
 
import React, { useEffect, useState } from 'react'
import { useSession } from "next-auth/react"; // Important
 
export default function UserPage() {
  const session = useSession(); // useSession hook as provided by NextAuthProvider
  const user = session?.data?.user; // My custom object as modified in auth.ts
  const jwt = user?.jwt; // My custom object as modified in auth.ts
  /* 
    Eg:
    useEffect hook to fetch favourite items in strapi passing jwt and setting the state from response
  */
  return (
    <div>
      user name: {user?.name}
      user email: {user?.email}
    </div>
  );
}
Last updated on