Skip to content
This repository has been archived by the owner on Sep 21, 2023. It is now read-only.

Migration to new Google Identity Services for Web login #502

Open
anmol-bst opened this issue Feb 28, 2022 · 43 comments
Open

Migration to new Google Identity Services for Web login #502

anmol-bst opened this issue Feb 28, 2022 · 43 comments

Comments

@anmol-bst
Copy link

Will the plugin be providing the support and migration to the new standard google identity based login that returns a CredentialResponse rather than a userToken as google is "discontinuing the Google Sign-In JavaScript Platform Library for web".

@devchris
Copy link

This would be really good to know. Thank you 🙇

@radekzz
Copy link

radekzz commented Mar 10, 2022

I'd also love to have support after March 31. 🙏 For reference https://developers.google.com/identity/oauth2/web/guides/migration-to-gis

@robdunn
Copy link

robdunn commented Mar 10, 2022

+1 on this. Thanks you!

@mfurniss
Copy link

+1 We just got an email from Google asking us to migrate to Google Identity Services.

@onkar-rentomojo
Copy link

+1

@miralmsojitra
Copy link

+1 Just incase, Is there any alternative?

@Ibrahim-Noor
Copy link

+1

@devchris
Copy link

@anthonyjgrove Do you have any estimates on this? Or is this project on ice? :D

@anthonyjgrove
Copy link
Owner

anthonyjgrove commented Mar 13, 2022

I haven't had the time with work and having a baby recently.

Would be happy to help plan out the work and review a 6.x release that migrates the hook to the new Google API though.

@devchris
Copy link

devchris commented Mar 14, 2022

@anthonyjgrove Congratulations 🥳

Since this will stop working for existing users March 31 in 2023, we still have some time. Although new users of the google api have a shorter deadline...

I might have some time in the near future (after May) to spike something out. Maybe we can get this upgrade in as a group effort 😄

@softmarshmallow
Copy link

Whether this project supports new gis on time or not, it would be necessary to have a consent / disclaimer about this so new developers won't accidentally integrate using this. -> on README

@WPaczula
Copy link

I'm happy to contribute to the migration process, let me know once there are some tasks I can help with

@talatkuyuk
Copy link

+1

As of today I receive below message in the console.

react_devtools_backend.js:3973 Your client application uses libraries for user authentication or authorization that will soon be deprecated. See the [Migration Guide](https://developers.google.com/identity/gsi/web/guides/gis-migration) for more information.

@reyarqueza
Copy link

reyarqueza commented Apr 22, 2022

I take it new users can still use react-google-login so long as their client id has a timestamp before April 30th, 2022, is that correct? That gives even new users a year before the March 31, 2023 date:

 Beginning April 30th, 2022 new web applications must use the Google Identity Services library, existing web apps may continue using the Platform Library until the March 31, 2023 deprecation date.

Don't forget to create 2 client ids. One for dev and one for prod.

@talaikis
Copy link

talaikis commented Apr 29, 2022

Yesterday I've implemented the new login on my site, it was easier than to use any library. Will share the code later, it's just several lines.

@deepakmewada
Copy link

@talaikis can you share the code here?

@thebrucecgit
Copy link

thebrucecgit commented Apr 29, 2022

Here is what I am using.

First I have a general hook called useScript that can load any <script> tag into the HTML head and has a callback function for when the script fully loads:

import { useEffect } from "react";

const useScript = (url, onload) => {
  useEffect(() => {
    const script = document.createElement("script");

    script.src = url;
    script.onload = onload;

    document.head.appendChild(script);

    return () => {
      document.head.removeChild(script);
    };
  }, [url, onload]);
};

export default useScript;

Then I have created a GoogleLogin component that loads Google's button.

import { useRef } from "react";
import useScript from "hooks/useScript";

export default function GoogleLogin({
  onGoogleSignIn = () => {},
  text = "signin_with",
  // feel free to add more options here
}) {
  const googleSignInButton = useRef(null);

  useScript("https://accounts.google.com/gsi/client", () => {
    window.google.accounts.id.initialize({
      client_id: process.env.REACT_APP_GOOGLE_CLIENT_ID,
      callback: onGoogleSignIn,
    });
    window.google.accounts.id.renderButton(
      googleSignInButton.current,
      { theme: "outline", size: "large", text, width: "250" } // customization attributes
    );
  });

  return <div className="test" ref={googleSignInButton}></div>;
}

Pretty straightforward!

Edit: I no longer suggest using my snippets above, as they can lead to unnecessary script fetches. I suggest using the @react-oauth package below as it's a better implementation.

@talaikis
Copy link

talaikis commented Apr 29, 2022

@talaikis can you share the code here?

I've said, I'll share :) Here it is. It's for Next.js, but can be adjusted.

Step 1:

Include somewhere (_app.js)

import Script from 'next/script'

<Script src="https://accounts.google.com/gsi/client" />

Step 2:

const onResponse = async ({ credential }) => {
  // send `credential` to backend
}

const onClick = () => {
  window.google.accounts.id.initialize({
    client_id: clientId, // here's your Google ID
    callback: onResponse,
    auto_select: false
  })
  window.google.accounts.id.prompt()
}

return <button onClick={onClick}>Sign in with Google</button>

Step 3: decode the token:

import { OAuth2Client } from 'google-auth-library'


async function verify (idToken) {
  const ticket = await client.verifyIdToken({
    idToken,
    audience: clientId
  })
  return ticket.getPayload()
}

// user data, like payload.email, etc.
const payload = await verify(credential)

@Poujhit
Copy link

Poujhit commented May 3, 2022

Here is what I am using.

First I have a general hook called useScript that can load any <script> tag into the HTML head and has a callback function for when the script fully loads:

import { useEffect } from "react";

const useScript = (url, onload) => {
  useEffect(() => {
    const script = document.createElement("script");

    script.src = url;
    script.onload = onload;

    document.head.appendChild(script);

    return () => {
      document.head.removeChild(script);
    };
  }, [url, onload]);
};

export default useScript;

Then I have created a GoogleLogin component that loads Google's button.

import { useRef } from "react";
import useScript from "hooks/useScript";

export default function GoogleLogin({
  onGoogleSignIn = () => {},
  text = "signin_with",
  // feel free to add more options here
}) {
  const googleSignInButton = useRef(null);

  useScript("https://accounts.google.com/gsi/client", () => {
    window.google.accounts.id.initialize({
      client_id: process.env.REACT_APP_GOOGLE_CLIENT_ID,
      callback: onGoogleSignIn,
    });
    window.google.accounts.id.renderButton(
      googleSignInButton.current,
      { theme: "outline", size: "large", text, width: "250" } // customization attributes
    );
  });

  return <div className="test" ref={googleSignInButton}></div>;
}

Pretty straightforward!

This works like a charm. In the callback function you will get CredentialResponse from google oauth. You have to decode the credential (which is jwt token). Here is a doc relating to that from Google https://developers.google.com/identity/gsi/web/guides/handle-credential-responses-js-functions#handle_credential_response

@deepakmewada
Copy link

@thebrucecgit Thanks

@phenry20
Copy link

phenry20 commented May 5, 2022

@thebrucecgit @talaikis Thanks for the code snippets, but how do you handle authorization scopes?

@talaikis
Copy link

talaikis commented May 6, 2022

@thebrucecgit @talaikis Thanks for the code snippets, but how do you handle authorization scopes?

An ID token has replaced OAuth2 access tokens and scopes.

@ArdiansDev
Copy link

how to get access_token on call back?

@kasvith
Copy link

kasvith commented May 8, 2022

For anyone who is struggling, you can use the following lib instead.
I used this for one my apps and it works effortlessly

https://www.npmjs.com/package/@react-oauth/google

@Rec0iL99
Copy link

how to get access_token on call back?

Here is what I am using.
First I have a general hook called useScript that can load any <script> tag into the HTML head and has a callback function for when the script fully loads:

import { useEffect } from "react";

const useScript = (url, onload) => {
  useEffect(() => {
    const script = document.createElement("script");

    script.src = url;
    script.onload = onload;

    document.head.appendChild(script);

    return () => {
      document.head.removeChild(script);
    };
  }, [url, onload]);
};

export default useScript;

Then I have created a GoogleLogin component that loads Google's button.

import { useRef } from "react";
import useScript from "hooks/useScript";

export default function GoogleLogin({
  onGoogleSignIn = () => {},
  text = "signin_with",
  // feel free to add more options here
}) {
  const googleSignInButton = useRef(null);

  useScript("https://accounts.google.com/gsi/client", () => {
    window.google.accounts.id.initialize({
      client_id: process.env.REACT_APP_GOOGLE_CLIENT_ID,
      callback: onGoogleSignIn,
    });
    window.google.accounts.id.renderButton(
      googleSignInButton.current,
      { theme: "outline", size: "large", text, width: "250" } // customization attributes
    );
  });

  return <div className="test" ref={googleSignInButton}></div>;
}

Pretty straightforward!

This works like a charm. In the callback function you will get CredentialResponse from google oauth. You have to decode the credential (which is jwt token). Here is a doc relating to that from Google https://developers.google.com/identity/gsi/web/guides/handle-credential-responses-js-functions#handle_credential_response

@ArdiansDev refer to this doc by Google.

@opalkonrad
Copy link

@thebrucecgit how could you deal with "TS2339: Property 'google' does not exist on type 'Window & typeof globalThis'" when using TypeScript?

@deepakmewada
Copy link

deepakmewada commented May 17, 2022

@opalkonrad Typescript file :

import React, { useRef } from "react";
import useScript from "../../../utils/useScript";
import { googleClientId } from "../../../config";

interface Props {
  text: String;
  onGoogleSignIn: Function;
}

declare const window: Window &
   typeof globalThis & {
     google: any;
     GoogleAuth:any;
   }

const GoogleLogin: React.FunctionComponent<Props> = ({
  onGoogleSignIn = () => {},
  text = "signin_with",
  // feel free to add more options here
}) => {
  const googleSignInButton = useRef<HTMLDivElement>(null);

  useScript("https://accounts.google.com/gsi/client", () => {
    window.google.accounts.id.initialize({
      client_id: googleClientId,
      callback: onGoogleSignIn,
    });
    window.google.accounts.id.renderButton(
      googleSignInButton.current,
      { theme: "outline", size: "large", text, width: "250" } // customization attributes
    );
  });

  return (
    <div className="test" ref={googleSignInButton}></div>
  );
}

export default GoogleLogin;

@MomenSherif
Copy link

MomenSherif commented May 17, 2022

I created a small wrapper around new Google identity service SDK @react-oauth/google,

it covers the different ways google can handle login these days and it's strongly typed

@manojkmishra
Copy link

manojkmishra commented May 17, 2022

As mentioned by @kasvith and @MomenSherif , we can use @react-oauth/google and get the userInfo as below


import { GoogleOAuthProvider } from '@react-oauth/google';  
import { GoogleLogin } from '@react-oauth/google';  

---------------------------------------------------
const googleSuccess =  async (res) => {  
    console.log('auth.js-googlesuccess-res',res)  
    fetch(`https://oauth2.googleapis.com/tokeninfo?id_token=${res.credential}`)
      .then(res => res.json())
      .then(response => {
        console.log('user Info=',response)
      })
      .catch(error => console.log(error));    
  };
  const googleError = (error) => {
    console.log('google signin failed-error',error)
}
--------------------------------------------------

<GoogleOAuthProvider clientId="CLIENT_ID">
          <GoogleLogin            
            onSuccess={googleSuccess}
            onFailure={googleError}     
          />
</GoogleOAuthProvider>

@MomenSherif
Copy link

@manojkmishra yup 🎉 and if you are using GoogleLogin you can decode res.credential (jwt) for user info without need for extra request

@CharleneKwok
Copy link

CharleneKwok commented May 21, 2022

@MomenSherif Hi! Thanks for your sharing. When I try to get the userInfo by using the code @manojkmishra mentioned. It worked on but seems not work on custom login button. It showed error {error: 'invalid_token', error_description: 'Invalid Value'} and said res.credential is undefined. So how do I get userInfo on custom login button?

@Rec0iL99
Copy link

@MomenSherif Hi! Thanks for your sharing. When I try to get the userInfo by using the code @manojkmishra mentioned. It worked on but seems not work on custom login button. It showed error {error: 'invalid_token', error_description: 'Invalid Value'} and said res.credential is undefined. So how do I get userInfo on custom login button?

Hi @CharleneKwok, I think the comments in this issue should help you out

MomenSherif/react-oauth#6

@MomenSherif
Copy link

Hello @CharleneKwok

'access_token' provided by google is used to authorize us to communicate with google APIs

To get user info from custom button you can follow the issue mentioned by @Rec0iL99

@CharleneKwok
Copy link

@MomenSherif Hi! Thanks for your sharing. When I try to get the userInfo by using the code @manojkmishra mentioned. It worked on but seems not work on custom login button. It showed error {error: 'invalid_token', error_description: 'Invalid Value'} and said res.credential is undefined. So how do I get userInfo on custom login button?

Hi @CharleneKwok, I think the comments in this issue should help you out

MomenSherif/react-oauth#6

Thank you so much! I can get userInfo now

@opalkonrad
Copy link

What about token refresh? How can I do that?

@MomenSherif
Copy link

MomenSherif commented May 22, 2022

@opalkonrad You need to follow google authorization code flow.
If you are using @react-oauth/google, It can be done using useGoogleLogin with flow: 'auth-code'

will return code that you will exchange with your backend to obtain

  • access_token (to talk with google APIs)
  • refresh_token (to refresh user's token)
  • id_token (JWT contains all user's info)

Client

const googleLogin = useGoogleLogin({
  onSuccess: async ({ code }) => {
    const tokens = await axios.post('http://localhost:3001/auth/google', {  // http://localhost:3001/auth/google backend that will exchange the code
      code,
    });

    console.log(tokens);
  },
  flow: 'auth-code',
});

Backend using express

require('dotenv').config();
const express = require('express');
const {
  OAuth2Client,
} = require('google-auth-library');
const cors = require('cors');

const app = express();

app.use(cors());
app.use(express.json());

const oAuth2Client = new OAuth2Client(
  process.env.CLIENT_ID,
  process.env.CLIENT_SECRET,
  'postmessage',
);


app.post('/auth/google', async (req, res) => {
  const { tokens } = await oAuth2Client.getToken(req.body.code); // exchange code for tokens
  console.log(tokens);
  
  res.json(tokens);
});

app.post('/auth/google/refresh-token', async (req, res) => {
  const user = new UserRefreshClient(
    clientId,
    clientSecret,
    req.body.refreshToken,
  );
  const { credentials } = await user.refreshAccessToken(); // optain new tokens
  res.json(credentials);
})

app.listen(3001, () => console.log(`server is running`));

@ariccio
Copy link

ariccio commented Aug 4, 2022

So, I'm going to need to drop this package, or will it be fixable with a patch?

@StarkHK
Copy link

StarkHK commented Nov 1, 2022

Here is what I am using.

First I have a general hook called useScript that can load any <script> tag into the HTML head and has a callback function for when the script fully loads:

import { useEffect } from "react";

const useScript = (url, onload) => {
  useEffect(() => {
    const script = document.createElement("script");

    script.src = url;
    script.onload = onload;

    document.head.appendChild(script);

    return () => {
      document.head.removeChild(script);
    };
  }, [url, onload]);
};

export default useScript;

Then I have created a GoogleLogin component that loads Google's button.

import { useRef } from "react";
import useScript from "hooks/useScript";

export default function GoogleLogin({
  onGoogleSignIn = () => {},
  text = "signin_with",
  // feel free to add more options here
}) {
  const googleSignInButton = useRef(null);

  useScript("https://accounts.google.com/gsi/client", () => {
    window.google.accounts.id.initialize({
      client_id: process.env.REACT_APP_GOOGLE_CLIENT_ID,
      callback: onGoogleSignIn,
    });
    window.google.accounts.id.renderButton(
      googleSignInButton.current,
      { theme: "outline", size: "large", text, width: "250" } // customization attributes
    );
  });

  return <div className="test" ref={googleSignInButton}></div>;
}

Pretty straightforward!

Edit: I no longer suggest using my snippets above, as they can lead to unnecessary script fetches. I suggest using the @react-oauth package below as it's a better implementation.

I found a easy solution for this,
in GoogleLogin function use a useEffect hook
useEffect({
return () => {
window.google = undefined;
}
}, [])

and in the useScript hook

const useScript = (url, onload) => {
useEffect(() => {
if (typeof window.google === "undefined") {
const script = document.createElement("script");

script.src = url;
script.onload = onload;

document.head.appendChild(script);

return () => {
  document.head.removeChild(script);
};

}
}, [url, onload]);
};

@th-harsh
Copy link

th-harsh commented Nov 15, 2022

@anthonyjgrove Do you have any estimates on this? Or is this project on ice? :D

@Georgi-Filipov
Copy link

@MomenSherif Will your library work after March 31st?

@MomenSherif
Copy link

@Georgi-Filipov Yes it will

@ariccio
Copy link

ariccio commented Jan 17, 2023

I'm curious, is the jury out on the best choices to make here? I have (of course) put it off until the last minute, but Im still wondering if it's worth doing manually or if there's a nice package already. People using @MomenSherif's package in production?

@JonHolman
Copy link

I have a question regarding the cutover to Google Identity Services. It is not specifically about the solution we are discussing here, but I plan to follow the great advice above once I figure out what I'm trying to fix :). I thought that the "old way" would no longer work if I created a new Google Client ID. I'm trying to confirm what does not work, so I can be sure that I've fixed it when I've implemented the "new way." I just tried https://github.com/LucasAndrad/gapi-script-live-example with a new client ID, and it seems to be working fine. That's my current confusion. I'm pretty sure that gapi-script library uses the "old way ."I hope somewhere here can help me learn what I am missing. Thank you for your time.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests