anthonyjgrove / react-google-login

A React Google Login Component
https://anthonyjgrove.github.io/react-google-login
MIT License
1.85k stars 426 forks source link

Migration to new Google Identity Services for Web login #502

Open anmol-bst opened 2 years ago

anmol-bst commented 2 years ago

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 commented 2 years ago

This would be really good to know. Thank you 🙇

radekzz commented 2 years ago

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

robdunn commented 2 years ago

+1 on this. Thanks you!

mfurniss commented 2 years ago

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

onkar-rentomojo commented 2 years ago

+1

miralmsojitra commented 2 years ago

+1 Just incase, Is there any alternative?

Ibrahim-Noor commented 2 years ago

+1

devchris commented 2 years ago

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

anthonyjgrove commented 2 years ago

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 commented 2 years ago

@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 commented 2 years ago

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 commented 2 years ago

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

talatkuyuk commented 2 years ago

+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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

@talaikis can you share the code here?

thebrucecgit commented 2 years ago

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 commented 2 years ago

@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 commented 2 years ago

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 commented 2 years ago

@thebrucecgit Thanks

phenry20 commented 2 years ago

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

talaikis commented 2 years ago

@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 commented 2 years ago

how to get access_token on call back?

kasvith commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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

deepakmewada commented 2 years ago

@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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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

CharleneKwok commented 2 years ago

@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 commented 2 years ago

@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

https://github.com/MomenSherif/react-oauth/issues/6

MomenSherif commented 2 years ago

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 commented 2 years ago

@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 commented 2 years ago

What about token refresh? How can I do that?

MomenSherif commented 2 years ago

@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

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 commented 2 years ago

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

StarkHK commented 2 years ago

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 commented 2 years ago

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

Georgi-Filipov commented 1 year ago

@MomenSherif Will your library work after March 31st?

MomenSherif commented 1 year ago

@Georgi-Filipov Yes it will

ariccio commented 1 year ago

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 commented 1 year ago

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.