react-oauth: GoogleLogin and useGoogleLogin are not returning the same response

How can I use a custom login button? GoogleLogin component and useGoogleLogin aren’t returning the same response

With GoogleLogin the response is:

{
  clientId: 'XXXXXX,
  credential: 'credential_token',
  select_by: 'btn'
}

With useGoogleLogin, the response is:

{
  access_token: "xxxxxxxxx",
  authuser: "0",
  expires_in: 3599,
  hd: "domain.com"
  prompt: "none"
  scope: "email profile openid https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email"
  token_type: "Bearer"
}

The hook isn’t supposed to return the credential key/value as the component does? I am sending the credential key/value to my server which verifies it to send me back the user profile.

import { OAuth2Client } from 'google-auth-library'
// ...
const userProfile = googleClient.verifyIdToken({
    idToken: req.body.credentials,
    audience: req.body.clientId,
  })

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 4
  • Comments: 28 (6 by maintainers)

Most upvoted comments

Authenticating the user involves obtaining an ID token and validating it. ID tokens are a standardized feature of OpenID Connect designed for use in sharing identity assertions on the Internet.

You can get id_token (JWT) if you are using the personalized button for authentication.

and useGoogleLogin hook is wrapping the Authorization part in new Google SDK if you are using it in implicit flow like that, it will return access_token to be used for fetching data from google APIs for example

  const googleLogin = useGoogleLogin({
    onSuccess: async tokenResponse => {
      console.log(tokenResponse);
      // fetching userinfo can be done on the client or the server
      const userInfo = await axios
        .get('https://www.googleapis.com/oauth2/v3/userinfo', {
          headers: { Authorization: `Bearer ${tokenResponse.access_token}` },
        })
        .then(res => res.data);

      console.log(userInfo);
    },
    // flow: 'implicit', // implicit is the default
  });

But my recommendation for you as you have a backend, go with Authorization code flow, which 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`));

Thank you so much for the authorization code flow example, @MomenSherif ! 🙇 It has been invaluable for a migration from the old Google Sign-in Library to Google Identity Services.

Just in case this saves anyone some of the headaches I have suffered debugging this: follow the example exactly as it is written! I thought I was being smart by inserting my app’s redirect URI in the backend snippet, but instead I spent an entire night searching online to try and understand why my app “doesn’t comply with Google’s OAuth 2.0 policy” or why I was getting a redirect_uri_mismatch error 🙃

🚨 In the backend OAuth2Client, the 3rd param (redirectUri) must be 'postmessage'! 🚨

Do not, I repeat, DO NOT try to be smart and add your app’s redirect URI there. It will cause a very unhelpful invalid_request error You can't sign in to this app because it doesn't comply with Google's OAuth 2.0 policy for keeping apps secure. You can let the app developer know that this app doesn't comply with one or more Google validation rules. or a slightly more helpful but still unclear redirect_uri_mismatch error.

Here’s the snippet in question from the example:

const oAuth2Client = new OAuth2Client(
  process.env.CLIENT_ID,
  process.env.CLIENT_SECRET,
  'postmessage', // <- LEAVE THIS AS-IS! Do NOT insert your actual redirect URI
);

As far as an actual explanation for why it must be 'postmessage' (or why Google’s docs neglect to mention it), all I’ve really found have been StackOverflow answers referencing an old Google+ platform sign-in doc:

Needed this for my Django backend. Here’s the code I got working for the views:

from django.http import JsonResponse
import requests
import core.settings as settings

def login(request):
    auth_code = request.GET['code']

    data = {
        'code': auth_code,
        'client_id': settings.SOCIAL_AUTH_GOOGLE_OAUTH2_KEY, # client ID
        'client_secret': settings.SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET, # client secret
        'redirect_uri': 'postmessage',
        'grant_type': 'authorization_code'
    }

    response = requests.post('https://oauth2.googleapis.com/token', data=data)

    return JsonResponse(response.json(), status=200)


def refresh(request):
    refresh_token = request.GET['refresh_token']

    data = {
        'refresh_token': refresh_token,
        'client_id': settings.SOCIAL_AUTH_GOOGLE_OAUTH2_KEY, # client ID
        'client_secret': settings.SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET, # client secret
        'grant_type': 'refresh_token'
    }

    response = requests.post('https://oauth2.googleapis.com/token', data=data)

    return JsonResponse(response.json(), status=200)

Of course this is not production ready (needs validation, remove hardcoded values, etc.), but it demonstrates the API calls to Google’s OAuth2 service.

@mistryrn there’s a special place in hell for developers of this api, right next to recaptcha creators

this absolutely HAS to be in the doc, spent like 5 hours figuring out, messaging support etc

@gusaiani unfortunately customizing the personalized button is very limited because Google renders it in an iframe and allows only certain options to be customizable through known props because they want the same look and feel across all applications.

All pops are listed here


Returning credential in custom hook, will be difficult as google doesn’t expose it implicitly,

  • implicit flow -> all we care is having user info back, we can use access_token google gave us to fetch certain data from them
 const googleLogin = useGoogleLogin({
    onSuccess: async tokenResponse => {
      console.log(tokenResponse);
      // fetching userinfo can be done on the client or the server
      const userInfo = await axios
        .get('https://www.googleapis.com/oauth2/v3/userinfo', {
          headers: { Authorization: `Bearer ${tokenResponse.access_token}` },
        })
        .then(res => res.data);

      console.log(userInfo);
    },
    // flow: 'implicit', // implicit is the default
  });

just wanted the package to be a small wrapper around the new SDK, with the same behaviors google gives us, and it’s upon the consumer to use it as his application wants.

if you need refresh token, or for some reason you need to get id_token (google’s JWT) from custom button, you will need the authorization code flow as mentioned above in the example.

you can tweak authorization flow to exchange code on the client side and ignore backend, but you will expose your client secret for any hackers.

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);
})

Thank you so much for this example. I just recently moved from react-google-login and the setup has been a breeze. I noticed you’re using UserRefreshClient in the code snippet - where does this come from?

A bit late to replying to this, sorry.

UserRefreshClient is included in the ‘google-auth-library’ package, so just import it via import { UserRefreshClient } from 'google-auth-library';.

@MomenSherif, thanks for the explanation above.

And thanks for this library. I’m certainly inclined to use it.

Having said what you said, would you still consider adding an option for the useGoogleLogin hook to have the request return credential?

Use case would be something like:

  1. we want to have a custom-looking button for the sign-in
  2. our back-end is not trivial and the back-end team is not able to prioritize work to implement what you describe above

Or maybe there are other ways to style the GoogleLogin component?

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);
})

Thank you so much for this example. I just recently moved from react-google-login and the setup has been a breeze. I noticed you’re using UserRefreshClient in the code snippet - where does this come from?

Needed this for my Django backend. Here’s the code I got working for the views:

from django.http import JsonResponse
import requests
import core.settings as settings

def login(request):
    auth_code = request.GET['code']

    data = {
        'code': auth_code,
        'client_id': settings.SOCIAL_AUTH_GOOGLE_OAUTH2_KEY, # client ID
        'client_secret': settings.SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET, # client secret
        'redirect_uri': 'postmessage',
        'grant_type': 'authorization_code'
    }

    response = requests.post('https://oauth2.googleapis.com/token', data=data)

    return JsonResponse(response.json(), status=200)


def refresh(request):
    refresh_token = request.GET['refresh_token']

    data = {
        'refresh_token': refresh_token,
        'client_id': settings.SOCIAL_AUTH_GOOGLE_OAUTH2_KEY, # client ID
        'client_secret': settings.SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET, # client secret
        'grant_type': 'refresh_token'
    }

    response = requests.post('https://oauth2.googleapis.com/token', data=data)

    return JsonResponse(response.json(), status=200)

Of course this is not production ready (needs validation, remove hardcoded values, etc.), but it demonstrates the API calls to Google’s OAuth2 service.

I am continuously running into an error when doing it this way.

{'error': 'redirect_uri_mismatch', 'error_description': 'Bad Request'}

However, on the frontend, this is what my code looks like.

const Login = () => {
	const login = useGoogleLogin({
		onSuccess: (codeResponse) => {
			console.log(
				axios.post("http://localhost:8005/api/users/login/", codeResponse)
			)
		},
		flow: "auth-code",
		redirect_uri: "http://localhost:3000/",
	})

	return <Button onClick={() => login()}>Sign in with Google 🚀 </Button>
}

And on the backend

@action(detail=False, methods=["POST"])
    def login(self, request):
        env = environ.Env()
        env.read_env()

        auth_code = request.data["code"]

        data = {
            "code": auth_code,
            "client_id": env("GOOGLE_CLIENT_ID"),  # client ID
            "client_secret": env("GOOGLE_CLIENT_SECRET"),  # client secret
            "redirect_uri": "http://localhost:3000/",
            "grant_type": "authorization_code",
        }

        response = requests.post("https://oauth2.googleapis.com/token", data=data)

        return Response(response.json(), status=status.HTTP_200_OK)

As you can see, it’s exactly the same as yours. How did you get past the redirect URI error?

EDIT: wtf, scroll up guys, I don’t understand why either, but it really is just replacing your redirect_uri on backend with ‘postmessage’. wtf google seriously??

If someone use dj-rest-auth and django-allauth, and encounter error redirect uri mismatch

you can see this comment https://github.com/iMerica/dj-rest-auth/issues/525#issuecomment-1675885190 which set callback_url = "postmessage" manually

class GoogleLogin(
    SocialLoginView
):  # if you want to use Authorization Code Grant, use this
    adapter_class = GoogleOAuth2Adapter
    callback_url = "postmessage"
    client_class = OAuth2Client

Thank you so much for the authorization code flow example, @MomenSherif ! 🙇 It has been invaluable for a migration from the old Google Sign-in Library to Google Identity Services.

Just in case this saves anyone some of the headaches I have suffered debugging this: follow the example exactly as it is written! I thought I was being smart by inserting my app’s redirect URI in the backend snippet, but instead I spent an entire night searching online to try and understand why my app “doesn’t comply with Google’s OAuth 2.0 policy” or why I was getting a redirect_uri_mismatch error 🙃

🚨 In the backend OAuth2Client, the 3rd param (redirectUri) must be 'postmessage'! 🚨

Do not, I repeat, DO NOT try to be smart and add your app’s redirect URI there. It will cause a very unhelpful invalid_request error You can't sign in to this app because it doesn't comply with Google's OAuth 2.0 policy for keeping apps secure. You can let the app developer know that this app doesn't comply with one or more Google validation rules. or a slightly more helpful but still unclear redirect_uri_mismatch error.

Here’s the snippet in question from the example:

const oAuth2Client = new OAuth2Client(
  process.env.CLIENT_ID,
  process.env.CLIENT_SECRET,
  'postmessage', // <- LEAVE THIS AS-IS! Do NOT insert your actual redirect URI
);

As far as an actual explanation for why it must be 'postmessage' (or why Google’s docs neglect to mention it), all I’ve really found have been StackOverflow answers referencing an old Google+ platform sign-in doc:

Thanks man!

Thank you very much, @MomenSherif.

Thanks @MomenSherif for the details. I could make it work with your code snippet. Just curious, When should I call the refresh token endpoint for Google auth? At every new session?

Thanks for this package. it’s very clean.

I followed all the steps in @MomenSherif’s post exactly but I am still ending up with an error related to an unauthorized client.

Basically I’m taking the code from the user from the frontend and sending to the backend, from where it’s supposed to exchange the code for an accesstoken using google’s api, but I keep getting an error 401: unauthorized_client, but I have enabled the calendar API on my project. I’m not sure what is going wrong.

Here’s the frontend code:

const googleLogin = useGoogleLogin({
        flow: "auth-code",
        onSuccess: async codeResponse => {
            console.log(codeResponse);

            const tokens = await axios.post("http://localhost:3001/auth/google/", {
                code: codeResponse.code
            });

            console.log(tokens);
        }
    })

and here’s the backend:

app.post('/auth/google', async (req, res) => {
    console.log("got request!")
    console.log(req.body.code)
    const tokens = await axios.post("https://oauth2.googleapis.com/token", {
        'code': req.body.code,
        'client_id': CLIENT_ID,
        'client_secret': CLIENT_SECRET,
        'redirect_uri': 'postmessage',
        'grant_type': 'authorization_code'
    });
    console.log(tokens);
    res.json(tokens);
});

Stack overflow link: https://stackoverflow.com/questions/74132586/accessing-google-calendar-api-using-service-account

AHh never mind I solved it - I was using a different oauth client ID for frontend/backend. After using the same IDs it worked.

@kharithomas It’s on backend side (express server in this example) if you want to use refresh token

If you just need user’s token you can use <GoogleLogin /> it’s straight forward

Or if you need custom button you can use useGoogleLogin, and check demo website in the Readme, will show you how to get user info step by step

@gusaiani Most welcome 😃

@eakl Most welcome ❤

you can set a timer to refresh the token with a new one before expiration, the timer can be set after user login, or the app initialized with a refresh token available

another implementation is to setup an interceptor for your requests to validate token expiration time, and if it will expire soon it will fire a request to refresh the token.

Any approach of them makes sense and is easy to implement for you, Go ahead with it 🎉