DEV Community

Cover image for Google One Tap Authentication in Next.js
Jakaria Masum
Jakaria Masum

Posted on

Google One Tap Authentication in Next.js

Google One Tap is a streamlined authentication method that allows users to sign in to your application with a single tap, using their Google account. In this blog post, we'll walk through the process of implementing Google One Tap in a Next.js application using NextAuth.js.

Prerequisites

Before we begin, make sure you have:

  1. A Next.js project set up
  2. NextAuth.js installed in your project
  3. A Google Cloud Console project with OAuth 2.0 credentials

Step 1: Set up Auth Options for next-auth configuration

First, let's configure NextAuth.js to work with Google authentication.
Create a file src/utils/authOptions.ts:

import { NextAuthOptions } from "next-auth";
import GoogleProvider from "next-auth/providers/google";

export const authOptions: NextAuthOptions = {
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
  ],
  callbacks: {
    async signIn({ user, account }) {
      if (account?.provider === "google") {
        try {
          console.log("user:", user, "\naccount:", account);
          return true;
        } catch (error) {
          console.error("Error during OAuth login:", error);
          return false;
        }
      }
      return true;
    },
  },

  secret: process.env.NEXTAUTH_SECRET,
};
Enter fullscreen mode Exit fullscreen mode

In console.log you will get user account information like name,image,email etc which can be used for other works such stores in database.

Step 2: Set Up NextAuth.js

Create a file app/api/auth/[...nextauth]/route.ts:

import { authOptions } from "@/utils/authOptions";
import NextAuth from "next-auth";

const handler = NextAuth(authOptions);

export { handler as GET, handler as POST };

Enter fullscreen mode Exit fullscreen mode

Step 3: Create a Google One Tap Component

Next, let's create a GoogleOneTap component that will handle the Google One Tap functionality:

"use client";

import { useEffect, useCallback, useState } from "react";
import { signIn, useSession } from "next-auth/react";
import Script from "next/script";

declare global {
  interface Window {
    google: {
      accounts: {
        id: {
          initialize: (config: any) => void;
          prompt: (callback: (notification: any) => void) => void;
          cancel: () => void;
          revoke: (hint: string, callback: () => void) => void;
        };
      };
    };
  }
}

export default function GoogleOneTap() {
  const { data: session } = useSession();
  const [isGoogleScriptLoaded, setIsGoogleScriptLoaded] = useState(false);
  console.log(session);

  const handleCredentialResponse = useCallback((response: any) => {
    signIn("google", {
      credential: response.credential,
      redirect: false,
    }).catch((error) => {
      console.error("Error signing in:", error);
    });
  }, []);

  const initializeGoogleOneTap = useCallback(() => {
    if (window.google && !session) {
      try {
        window.google.accounts.id.initialize({
          client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID!,
          callback: handleCredentialResponse,
          context: "signin",
          ux_mode: "popup",
          auto_select: false,
          use_fedcm_for_prompt: true,
        });

        window.google.accounts.id.prompt((notification: any) => {
          if (notification.isNotDisplayed()) {
            console.log(
              "One Tap was not displayed:",
              notification.getNotDisplayedReason()
            );
          } else if (notification.isSkippedMoment()) {
            console.log(
              "One Tap was skipped:",
              notification.getSkippedReason()
            );
          } else if (notification.isDismissedMoment()) {
            console.log(
              "One Tap was dismissed:",
              notification.getDismissedReason()
            );
          }
        });
      } catch (error) {
        if (
          error instanceof Error &&
          error.message.includes(
            "Only one navigator.credentials.get request may be outstanding at one time"
          )
        ) {
          console.log(
            "FedCM request already in progress. Waiting before retrying..."
          );
          setTimeout(initializeGoogleOneTap, 1000);
        } else {
          console.error("Error initializing Google One Tap:", error);
        }
      }
    }
  }, [session, handleCredentialResponse]);

  useEffect(() => {
    if (isGoogleScriptLoaded) {
      initializeGoogleOneTap();
    }
  }, [isGoogleScriptLoaded, initializeGoogleOneTap]);

  useEffect(() => {
    if (session) {
      // If user is signed in, cancel any ongoing One Tap prompts
      window.google?.accounts.id.cancel();
    }
  }, [session]);

  return (
    <Script
      src="https://accounts.google.com/gsi/client"
      async
      defer
      onLoad={() => setIsGoogleScriptLoaded(true)}
      strategy="afterInteractive"
    />
  );
}
Enter fullscreen mode Exit fullscreen mode

If you want to make automatic signin using google then set auto_select into true.

Step 4: Integrate Google One Tap into Your main layout

Now, let's update your layout to include the Google One Tap component:

"use client";
import GoogleOneTap from "@/components/GoogleOneTap";
import { SessionProvider } from "next-auth/react";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <SessionProvider>
          {children}
          <GoogleOneTap />
        </SessionProvider>
      </body>
    </html>
  );
}
Enter fullscreen mode Exit fullscreen mode

The layout must be wrap with sessionProvider as it helps to detect the user information in our whole application.

Step 5: Set Up Home page to see the demo

"use client";

import { useSession, signOut } from "next-auth/react";

export default function Home() {
  const { data: session } = useSession();

  return (
    <div className="flex flex-col items-center justify-center min-h-screen py-2">
      <main className="flex flex-col items-center justify-center w-full flex-1 px-20 text-center">
        <h1 className="text-6xl font-bold">
          Welcome to{" "}
          <span className="text-blue-600">Google One tap login!</span>
        </h1>

        {session ? (
          <>
            <p className="mt-3 text-2xl">
              You are signed in as
              <div className="flex flex-col">
                <span>Name:{session.user?.name}</span>
                <span>Email: {session?.user?.email}</span>
              </div>
            </p>

            <button
              className="mt-4 px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
              onClick={() => signOut()}
            >
              Sign out
            </button>
          </>
        ) : (
          <p className="mt-3 text-2xl">
            You are not signed in. Google One Tap should appear shortly.
          </p>
        )}
      </main>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

Step 5: Set Up .env file

Create a .env.local file in your project root and add the following variables:

GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
NEXTAUTH_SECRET=your_nextauth_secret
NEXTAUTH_URL=http://localhost:3000
NEXT_PUBLIC_GOOGLE_CLIENT_ID=your_google_client_id
Enter fullscreen mode Exit fullscreen mode

Replace the placeholder values with your actual Google OAuth credentials.

Conclusion

With these steps, you've successfully implemented Google One Tap authentication in your Next.js application. Users can now sign in with a single click using their Google account.

Image description

Image description

Remember to handle error cases and edge scenarios in a production environment. Also, consider adding more robust user management features as needed for your application.

Happy coding!

Top comments (0)