How to implement Google One Tap Sign-In with NextAuth in 2023
--
This tutorial is based on this really good, yet a bit outdated, blog post from Fabrizio Ruggeri.
I will go through the steps illustrated by Fabrizio, adding more contextual information about NextAuth and fixing a few out-of-date details. I personally struggled a lot to follow his tutorial and I so I set out to make a more beginner-friendly version of it.
Overview
What we’ll need:
- A custom CredentialsProvider
- An invisible parent component to host the One Tap UI
- A custom hook to implement the sign-in logic
Support libraries:
google-auth-library
@types/google-on-tap
prisma
(i’m using prisma but you can use something else, the original example is with MongoDB)
What we’ll do:
We will create an invisible component, that hosts a custom hook. The custom hook, on render, uses NextAuth’s useSession
to see if there’s an active session. If there is no active session it shows the Google One Tap UI.
When the user clicks on the sign-in button the NextAuth signIn
function is called. This triggers a series of actions: checking if there is an existing session otherwise creating one, ditto for user and account (more info on this later).
This is pretty much it. Once the session is active NextAuth will be able to pick it up from anywhere in the client and the server with useSession
and getServerSession
.
Implementing a CustomCredentials Provider (backend)
The goal is to define what happens when a user tries to get authorized, in order to do this we need access to our database adapter, so in […nextauth].tsx
we have to use the advanced initialization.
The most important things to understand here to keep the general thread are:
- We are creating a custom
Provider
, with id ‘googleonetap
’ - This
Provider
comes with a set of credentials that we define as a single field calledcredential
of typetext
. - When an attempt at signing in is made with this provider, the
authorize
function is called and acredential
must be supplied. Thecredential
being a token generated by the Google backend (more on this in a minute).
Client Side Implementation (frontend)
If you are using TypeScript, before continuing make sure you have installed @types/google-on-tap
.
We will follow Fabrizio’s example and use the Javascript API, as opposed to the HTML API, stowing the request for the Google user token in a custom hook so to hide any sensitive information.
Importing the Google library on the client
To make the google library available on time and make sure that the Google One Tap UI renders correctly, you have to add the import in the _document.tsx
file. In fact, if you import it in _app.tsx
the library sometimes won’t be loaded in time and raise all types of annoying errors.
For this we use the Next.js Script
tag, as suggested by Fabrizio.
P.S. You may have seen this before implemented this way, placed in the document head
:
<script src=”https://accounts.google.com/gsi/client" async defer></script>
I decided to go with the Script
tag solution because more elegant and because it took advantage of framework-specific features.
The Custom Hook
This is where a good chunk of the magic happens. The key element here is the google.accounts.id.initialize
method of the Google Auth Javascript API. As you can guess from the name, it initializes the Google One Tap UI and it can be rendered in two separate ways. One is to simply render it and the other one is to append it to an exsiting html node.
We’re going to use the second method. So that we don’t have to write the intialization code directly in the page but we can stow it away and then append the initialization logic to an html node/component of our choice.
Basically at every new page render, if there is no active session the Google One Tap UI will be rendered, and if the user clicks on it, the signIn
function is called, passing the user token to the NextAuth backend, calling our CredentialsProvider googleonetap
‘s authorize
method and sending a request for the rest of the user’s info.
I got the idea for this imlementation from the NextAuth’s docs.
The invisible component
This component serves two purposes:
- calling the
useOneTapSignin
hook and thus intializing the Google One Tap UI - Defining it’s own position on the screen, which will correspond with the position where the Google One Tap UI will appear. In the example I’m using Tailwind CSS but whatever design system you’re using I recommend you to make it
sticky
so that it doesn’t slide out of the viewport when users scroll down the page.
You should stick this component in your Layout
component if you have one, otherwise in some other element that is present in every page.