Add the new Google Sign In to your React app!
TLDR: Scroll down and copy the code. You only need to add your login logic.
This article will cover:
- A brief introduction to the new-ish Google Sign In api
- How to implement it using React and Typescript
- Add relevant typings on global window object
Intro
Google recently announced they are discontinuing their old auth-service "Google Sign-In" in favor for their new and improved service "Sign In With Google".
Their new service comes in two parts:
- Login button
- One Tap
You can read more about them here.
We'll cover the first one in this article, how it works and how to implement it in React with Typescript.
Compared to the old service, this one is much easier to use. It's straight-forward enough to implement the login button yourself without needing a library like (the awesome) react-google-login
that's the go-to solution for the old api.
Google Auth Introduction
I'm just going to go over the basics here.
Disclaimer: There might be a much better way to do this. I would be happy to know how, so leave a comment! I couldn't find any examples of this, so I figured I'd post my implementation and hopefully help someone else.
Although the new auth api is a bit tricky to get your head around at first when using React, we can make it work. The trick is to understand how the script loads the client and how that fits with React's loading and rendering.
The google documentation covers both the html and javascript api, and we'll be using the latter. But since we're building with React, we mostly use the step-by-step guide to figure out how the auth api works. We have to account for how React loads and renders elements. Unfortunately this means we can't just statically just stick it in the header like the guide instructs.
After you followed the setup process, the documentation tells you to add a script tag to your header (in public/index.html
), but since we're using React we're not going to do that. We're going to control when and where we run that script, and thus initiate the google auth client.
// The script that runs and load the new google auth client.// We're not(!) adding it to our header like the guide says.<script src="https://accounts.google.com/gsi/client" async defer></script>
Lets get started
First off, Typescript will complain about missing types on the window
object. We'll fix that properly later.
What we'll implement first is adding the script that loads the google auth client when our sign-in page renders, add the "target div" that the script will be looking for, and initiate the client with our callback function.
The problem
Attaching that callback-function to the google client is what makes using the new auth api with React a bit troublesome. (but even more so using the old one!). If we add the script tag to the static html like the docs say, we can't pass it any function defined in react. We could maybe handle stuff by defining a function on the server-side of things, but I want to stay within React and handle this on the front-end and use my graphql-hooks to login.
The process
When our login page renders, we'll attach the google client-script to the header from inside a useEffect
hook. We'll add an initializer-function to the onLoad
-eventlistener for that script tag. The onLoad event will then trigger and initialize the google auth client with our callback attached.
The google client will then magically find our already rendered div
with id=g_id_signin
and render the login-button.
A nice looking, personalized google sign-in button should now be visible to the user.
The code
import { Button } from "@material-ui/core"import { useEffect, useState } from "react"export default function GoogleSignin() { const [gsiScriptLoaded, setGsiScriptLoaded] = useState(false) const [user, setUser] = useState(undefined) useEffect(() => { if (user?._id || gsiScriptLoaded) return const initializeGsi = () => { // Typescript will complain about window.google // Add types to your `react-app-env.d.ts` or //@ts-ignore it. if (!window.google || gsiScriptLoaded) return setGsiScriptLoaded(true) window.google.accounts.id.initialize({ client_id: GOOGLE_CLIENT_ID, callback: handleGoogleSignIn, }) } const script = document.createElement("script") script.src = "https://accounts.google.com/gsi/client" script.onload = initializeGsi script.async = true script.id = "google-client-script" document.querySelector("body")?.appendChild(script) return () => { // Cleanup function that runs when component unmounts window.google?.accounts.id.cancel() document.getElementById("google-client-script")?.remove() } }, [handleGoogleSignIn, initializeGsi, user?._id])const handleGoogleSignIn = (res: CredentialResponse) => { if (!res.clientId || !res.credential) return // Implement your login mutations and logic here. // Set cookies, call your backend, etc. setUser(val.data?.login.user) })}return <Button className={"g_id_signin"} />}
You might want to add some more implementation details here and there. But this is the gist of it! You can at least use it as a starting point. Hope it helps!
Fixing the window types
If you're using create-react-app
, you will already have the file react-app-env.d.ts
in your project root. You can add the types for the google auth api there. I translated the api documentation to typescript types. There might be some errors since I haven't used and tested all the functions. But it should be correct.
/// <reference types="react-scripts" />interface IdConfiguration { client_id: string auto_select?: boolean callback: (handleCredentialResponse: CredentialResponse) => void login_uri?: string native_callback?: Function cancel_on_tap_outside?: boolean prompt_parent_id?: string nonce?: string context?: string state_cookie_domain?: string ux_mode?: "popup" | "redirect" allowed_parent_origin?: string | string[] intermediate_iframe_close_callback?: Function}interface CredentialResponse { credential?: string select_by?: | "auto" | "user" | "user_1tap" | "user_2tap" | "btn" | "btn_confirm" | "brn_add_session" | "btn_confirm_add_session" clientId?: string}interface GsiButtonConfiguration { type: "standard" | "icon" theme?: "outline" | "filled_blue" | "filled_black" size?: "large" | "medium" | "small" text?: "signin_with" | "signup_with" | "continue_with" | "signup_with" shape?: "rectangular" | "pill" | "circle" | "square" logo_alignment?: "left" | "center" width?: string local?: string}interface PromptMomentNotification { isDisplayMoment: () => boolean isDisplayed: () => boolean isNotDisplayed: () => boolean getNotDisplayedReason: () => | "browser_not_supported" | "invalid_client" | "missing_client_id" | "opt_out_or_no_session" | "secure_http_required" | "suppressed_by_user" | "unregistered_origin" | "unknown_reason" isSkippedMoment: () => boolean getSkippedReason: () => | "auto_cancel" | "user_cancel" | "tap_outside" | "issuing_failed" isDismissedMoment: () => boolean getDismissedReason: () => | "credential_returned" | "cancel_called" | "flow_restarted" getMomentType: () => "display" | "skipped" | "dismissed"}interface Window { google?: { accounts: { id: { initialize: (input: IdConfiguration) => void prompt: ( momentListener: (res: PromptMomentNotification) => void ) => void renderButton: ( parent: HTMLElement, options: GsiButtonConfiguration, clickHandler: Function ) => void disableAutoSelect: Function storeCredential: Function<{ credentials: { id: string; password: string } callback: Function }> cancel: () => void onGoogleLibraryLoad: Function revoke: Function<{ hint: string callback: Function<{ successful: boolean; error: string }> }> } } }}
Shameless plug
If you like this kind of stuff and are looking for a job in Sweden, Gothenburg, hit me up!
Original Link: https://dev.to/mremanuel/add-the-new-google-sign-in-to-your-react-app-p6m
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To