Føj det nye Google Log ind til din React-app!

TLDR:Rul ned og kopier koden. Du behøver kun at tilføje din login-logik.

Denne artikel vil dække:

  • En kort introduktion til det nye Google Log In-api
  • Sådan implementeres det ved hjælp af React og Typescript
    • Tilføj relevante indtastninger på globalt vinduesobjekt

Intro

Google annoncerede for nylig, at de stopper deres gamle godkendelsestjeneste "Google Log-In" til fordel for deres nye og forbedrede tjeneste "Log ind med Google".

Deres nye service kommer i to dele:

  1. Login-knap
  2. Et tryk

Du kan læse mere om dem her.
Vi vil dække den første i denne artikel, hvordan det virker, og hvordan man implementerer det i React with Typescript.

Sammenlignet med den gamle tjeneste er denne meget nemmere at bruge. Det er ligetil nok at implementere login-knappen selv uden at skulle bruge et bibliotek som (den fantastiske) react-google-login det er go-to-løsningen til det gamle api.

Google Auth-introduktion

Jeg vil lige gennemgå det grundlæggende her.

Ansvarsfraskrivelse:Der er muligvis en meget bedre måde at gøre dette på. Jeg ville være glad for at vide hvordan, så læg en kommentar! Jeg kunne ikke finde nogle eksempler på dette, så jeg regnede med, at jeg ville poste min implementering og forhåbentlig hjælpe en anden.

Selvom det nye auth api er lidt vanskeligt at få hovedet omkring i starten, når du bruger React, kan vi få det til at fungere. Tricket er at forstå, hvordan scriptet indlæser klienten, og hvordan det passer med Reacts indlæsning og gengivelse.

Google-dokumentationen dækker både html- og javascript-api, og vi vil bruge sidstnævnte. Men da vi bygger med React, bruger vi for det meste trin-for-trin guiden til at finde ud af, hvordan auth api'en fungerer. Vi skal redegøre for, hvordan React indlæser og gengiver elementer. Desværre betyder det, at vi ikke bare statisk kan sætte det i overskriften, som guiden instruerer.

Når du har fulgt opsætningsprocessen, fortæller dokumentationen dig, at du skal tilføje et script-tag til din header (i public/index.html ), men da vi bruger React, er vi ikke vil gøre det. Vi kommer til at kontrollere, hvornår og hvor vi kører det script, og dermed starte google auth-klienten. Vi gør dette, fordi scriptet starter en klient, og vi ønsker at give det vores egen tilbagekaldsfunktion, som vi definerer med react.

// 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>

Lad os komme i gang

For det første vil Typescript klage over manglende typer på window objekt. Vi ordner det ordentligt senere.

Det, vi først implementerer, er at tilføje scriptet, der indlæser Google Auth-klienten, når vores login-side gengives, tilføje "target div", som scriptet leder efter, og starte klienten med vores tilbagekaldsfunktion.

Problemet

At knytte denne tilbagekaldsfunktion til Google-klienten er det, der gør det lidt besværligt at bruge det nye auth-api med React. (men endnu mere ved at bruge den gamle!). Hvis vi tilføjer script-tagget til den statiske html, som dokumenterne siger, kan vi ikke videregive nogen funktion defineret i react. Vi kunne måske håndtere ting ved at definere en funktion på serversiden af ​​tingene, men jeg vil gerne blive inden for React og håndtere dette på front-end og bruge mine graphql-hooks til at logge ind.

Processen

Når vores login-side gengives, vedhæfter vi Google-klient-scriptet til headeren inde fra en useEffect krog. Vi tilføjer en initialiseringsfunktion til onLoad -eventlistener for det script-tag. OnLoad-hændelsen vil derefter udløse og initialisere Google Auth-klienten med vores tilbagekald tilknyttet.

Google-klienten vil derefter på magisk vis finde vores allerede gengivet div med id=g_id_signin og gengiv login-knappen.

En flot, personlig google-logon-knap skulle nu være synlig for brugeren.

Koden

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"} />

}

Du vil måske tilføje nogle flere implementeringsdetaljer her og der. Men dette er kernen i det! Du kan i det mindste bruge det som udgangspunkt. Håber det hjælper!

Reparation af vinduestyperne

Hvis du bruger create-react-app , vil du allerede have filen react-app-env.d.ts i dit projektrod. Du kan tilføje typerne til google auth api der. Jeg oversatte api-dokumentationen til typescript-typer. Der kan være nogle fejl, da jeg ikke har brugt og testet alle funktionerne. Men det skal være korrekt.

/// <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 }>
        }>
      }
    }
  }
}

Skamløst stik

Hvis du kan lide denne slags ting og leder efter et job i Sverige, Gøteborg, så kontakt mig!