You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
+++
slug = "gapi-authentication-nextjs"
reading_time = "5min"
summary = "A complete guide on how to authenticate using gapi client side in a Next.js app."
+++
If you use any of the google API libraries in your website, it means you are using gapi. Using it with Next.js can be a bit tricky due to its nature of being 100% client side (as expected). This guide shows a neat way of dealing with private routes in your Next.js app.
🔧 Retrieving profile information with gapi
First, we need to load the gapi script client side. This is already tricky as it is a script that you have to load and then wait for it to initialise. Usually, the code will look something like this:
Now, this is not very useful yet as it just makes sure you download the script and is loaded. In order to make use of its clients like people, we need to load that specific client with:
At this point, you should be able to access and use window.gapi.client.people. Okay, let's organise all this code in a way that is reusable for our project. In order to do that, we are going to create a custom hook called useGapiClient that makes sure:
all the client libraries we want to use in our project are loaded too.
importReactfrom'react';// This avoids Next.js server side crashing with window not being definedconstisBrowser=typeofwindow!=='undefined';/** * Use this in any component that needs Gapi. This will make sure we load GAPI and trigger the * callback so a re-render happens. A typical use case: * * const [isGapiLoaded] = useGapiClient(); * if (isGapiLoaded) { * <do your stuff> * } */exportdefaultfunctionuseGapiClient(){const[isLoaded,setIsLoaded]=React.useState<boolean>(isBrowser&&!!window.gapi&&!!window.gapi.client,);React.useEffect(()=>{if(!window.gapi||!window.gapi.client){constscript=document.createElement('script');script.id='gapiScript';script.src='https://apis.google.com/js/api.js';script.async=true;script.onload=()=>loadGapiClient(setIsLoaded);document.body.appendChild(script);return()=>{document.body.removeChild(script);};}return()=>{};},[isLoaded]);return[isLoaded];}/** * This function makes sure gapi client is properly loaded together with the * client apis we will be using. */asyncfunctionloadGapiClient(callback: React.Dispatch<React.SetStateAction<boolean>>){if(!window.gapi.client){awaitnewPromise((res,rej)=>{if(window.gapi){window.gapi.load('client',res);}});window.gapi.client.setToken({access_token: localStorage.getItem('accessToken')asstring});awaitwindow.gapi.client.load('https://www.googleapis.com/discovery/v1/apis/people/v1/rest');awaitwindow.gapi.client.load('https://www.googleapis.com/discovery/v1/apis/drive/v3/rest');callback(true);}}
Note an important part here where we pass a callback to loadGapiClient and then call it as callback(true) once everything is loaded. This ensures that the hook renders again because it triggers a state update so we then returning isLoaded being true. This hook is now ready to be used in any of our components as follows:
We already have a powerful hook useGapiClient that allows us to load and use any Google API in our app. Our next step is to protect the private routes from our App using this. To do so, we will use yet another custom hook called useUser and the swr library which you can install with yarn add swr. Note this hook is heavily inspired from this example but we've adapted it to using gapi instead.
Note that with this simple logic, we are considering the user to be logged in if we are able to retrieve their profile information. If we receive a 401: Unauthorized back then we consider the user not logged in. Any other error we just bubble up. You can of course use any custom logic in getUser, whatever works for your app.
And then, you can use this to protect any Page in your app. In my case, I would recommended to have a shared layout for all those pages where you can reuse such logic. For example src/app/dashboard/layout.tsx:
importReactfrom'react';importuseUserfrom'@/hooks/useUser';exportdefaultfunctionDashboardLayout({
children,}: React.PropsWithChildren): JSX.Element{const{ user }=useUser();if(!user||user.isLoggedIn===false){return<div>Loading...</div>;}return({children});}
💚 Conclusion
useUser hook is very simple to use and provides a re-usable way of protecting any page you need in your app. Note that we haven't explore SWR library options but there are plenty of them to re-validate data, dealing with errors, etc. I recommend you to have a look at it to optimise the requests you do to your dependencies.
If you enjoyed the post feel free to react to it in the Github tracker (for now).
The text was updated successfully, but these errors were encountered:
+++
slug = "gapi-authentication-nextjs"
reading_time = "5min"
summary = "A complete guide on how to authenticate using gapi client side in a Next.js app."
+++
If you use any of the google API libraries in your website, it means you are using gapi. Using it with Next.js can be a bit tricky due to its nature of being 100% client side (as expected). This guide shows a neat way of dealing with private routes in your Next.js app.
🔧 Retrieving profile information with gapi
First, we need to load the gapi script client side. This is already tricky as it is a script that you have to load and then wait for it to initialise. Usually, the code will look something like this:
Now, this is not very useful yet as it just makes sure you download the script and is loaded. In order to make use of its clients like people, we need to load that specific client with:
At this point, you should be able to access and use
window.gapi.client.people
. Okay, let's organise all this code in a way that is reusable for our project. In order to do that, we are going to create a custom hook calleduseGapiClient
that makes sure:gapi.client
is loadedNote an important part here where we pass a callback to
loadGapiClient
and then call it ascallback(true)
once everything is loaded. This ensures that the hook renders again because it triggers a state update so we then returningisLoaded
being true. This hook is now ready to be used in any of our components as follows:🔒 Private routes
We already have a powerful hook
useGapiClient
that allows us to load and use any Google API in our app. Our next step is to protect the private routes from our App using this. To do so, we will use yet another custom hook calleduseUser
and theswr
library which you can install withyarn add swr
. Note this hook is heavily inspired from this example but we've adapted it to using gapi instead.The hook is super simple. Basically the logic is as follows:
getUser
if gapi is loaded, if not we skip the call.useEffect
when eitherisGapiLoaded
oruser
change values because those are the values that affect our logic.user
is undefined, we return because we are not ready yet (gapi is not loaded).user
is not logged in, we send the user to/account/login
so we can perform the action logging in.The
getUser
function looks like this:Note that with this simple logic, we are considering the user to be logged in if we are able to retrieve their profile information. If we receive a
401: Unauthorized
back then we consider the user not logged in. Any other error we just bubble up. You can of course use any custom logic ingetUser
, whatever works for your app.And then, you can use this to protect any Page in your app. In my case, I would recommended to have a shared
layout
for all those pages where you can reuse such logic. For examplesrc/app/dashboard/layout.tsx
:💚 Conclusion
useUser
hook is very simple to use and provides a re-usable way of protecting any page you need in your app. Note that we haven't explore SWR library options but there are plenty of them to re-validate data, dealing with errors, etc. I recommend you to have a look at it to optimise the requests you do to your dependencies.If you enjoyed the post feel free to react to it in the Github tracker (for now).
The text was updated successfully, but these errors were encountered: