MomenSherif / react-oauth

Google OAuth2 using the new Google Identity Services SDK for React 🚀
https://www.npmjs.com/package/@react-oauth/google
MIT License
1.07k stars 135 forks source link

Returned `access_token` is not a JWT token #203

Closed brunofin closed 1 year ago

brunofin commented 1 year ago

The library I used to use previously would return a response that contained a tokenId property that was a JWT string. Using this library, the token response is in access_token but this string is not a JWT compatible token.

Example: "ya29.a0AVvZVsoUlvR7ZOuVLg0cnbe7sON1Lv8VNPOIheq_Y-tk0a8d7XsdCbvMG-c-T-PoGYwrUPmcFmH5CHANGED-CHANGED-CHANGED9bNYRV8Hcwi2iTq5DQB23s8uKKPrsdEHHG5Z5UqVAdXwltrWAKEaCgYKAf8SARESFQGbdwaIWd578wabGtnjlSmZ-HVLug0166".

JWT tokens can be split in 3 different parts and are generally much longer than this. How can I get a proper JWT token using this library? Thanks.

jimcapel commented 1 year ago

I'm on V0.7.1 so may be different but:

GoogleLogin component's onSuccess prop gives us a CredentialResponse object, credentialResponse.credential is the JWT.

brunofin commented 1 year ago

I don't get credentials too. I am using the useGoogleLogin hook though. Also on v0.7.1.

Example object:

{
    "access_token": "ya29.a0AVvZVsoqgSkPj0P26D5wXXXXXXXXXXXXXXXXa-8tkFINrL92doOVHvE9pomKosHY_VmXXXXXXXXXXXXXX3a2CphcGgOZcJ-VuQ-G5rblV_4UIE6wfJfmj2PQ1NbV8HfhGaFSwB8JrOhSY1Kcnm88C3RrO2UaCgYKAeUSARESFQGbdwaIX7-DUs-_g-JlRLx0r-UKIg0166",
    "token_type": "Bearer",
    "expires_in": 3598,
    "scope": "email profile openid https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile",
    "authuser": "0",
    "prompt": "none"
}
brunofin commented 1 year ago

I see this is not supported by the hook, the mothods it use are different. It seems to be supported by useGoogleOneTapLogin but this one does not support render custom buttons.

I have forked the project and I am working on a simple solution to make useGoogleLogin work with that depending on flow property. I am adding a new possible value.

Will submit a PR when ready.

bvodola commented 1 year ago

@brunofin having the same issue here. Please, post your changes when you have them. Greatly appreciate it! 😬

brunofin commented 1 year ago

@bvodola I have started working on it yesterday, should be up today as I really need this. Cannot guarantee it will be merged in to this repo today though as it depends on the repo maintainers but feel free to use my fork meanwhile. Will let you know.

GZWZC commented 1 year ago

meet the same probleam, how to solve?

MomenSherif commented 1 year ago

@brunofin feel free to submit PR any time

returning access_token is just what google responds with for implicit flow, and if you wanna get user info you can call their user info api with the access_token similar to this https://react-oauth.vercel.app/images/Implicit-snap.png

or if you have backend, you can use authorization code flow integration with backend will return all possible hooks that google provide access_token user for authorization with google APIs, id_token which is jwt contains user info and refresh_token to refresh google token

brunofin commented 1 year ago

I have created a PR #209 but while it is not approved and merged in the repository, feel free to use the dependency from my forked repo. Use it at your own risk though.

Since this is a mono-repo, I needed to use the help of GitPkg to create a proper and a branch that contains the dist files (so I wouldn't need to setup my own npm package and publish it). This works for me.

In your package.json:

    "@react-oauth/google": "https://gitpkg.now.sh/brunofin/react-oauth/packages/%40react-oauth/google?dist",
Emiliano-Bucci commented 1 year ago

@brunofin Hi! I've installed your dependency and using the hook with flow: 'credential' i still don't get the tokenId. As you can see from the screen, i see the correct types but the response contains authUser, code, promp and scope Screenshot 2023-03-03 at 10 15 33

Any thoughts? Thanks in advance!

SilincaHoratiu18 commented 1 year ago

@brunofin any news regarding the PR that fixes the credentials response?

brunofin commented 1 year ago

BTW I needed to come back to this today, and I managed to make it work with the implicit workflow using the new token by making a small change to how the backend verifies the token, thus rendering the frontend changes I proposed here unnecessary, and honestly I wouldn't recommend using it anymore if you are able to follow those steps:

On the frontend, the only change I made was to make it refer to the response.access_code to send that to the backend for verification:

  const login = useGoogleLogin({
    flow: 'implicit',
    onSuccess: response => onSuccess?.(response.access_token),
    prompt: 'select_account',
    scope: 'openid email profile',
  });

On the backend side, things get a little more specific. We use C# .NET MVC Entity Framework 6. We used to use the library GoogleJsonWebSignature to verify the token with:

var payload = await GoogleJsonWebSignature.ValidateAsync(accessToken);

This would return an object with user info if the token was valid. That's where it fails with the new Google OAuth system, but you can create a drop-in replacement function and follow Google's recommendation to make an API call to https://www.googleapis.com/userinfo/v2/me in order to both verify the token and return the exact same information GoogleJsonWebSignature.ValidateAsync returns:

        public static async Task<GoogleJsonWebSignature.Payload> ValidateGoogleGISToken(string accessToken)
        {
            var client = new HttpClient();

            var request = new HttpRequestMessage(System.Net.Http.HttpMethod.Get,
                "https://www.googleapis.com/userinfo/v2/me");
            request.Headers.Authorization =
                new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);

            var response = await client.SendAsync(request);

            if (response.IsSuccessStatusCode)
            {
                var userInfo = await response.Content.ReadAsStringAsync();
                return JsonConvert.DeserializeObject<GoogleJsonWebSignature.Payload>(userInfo);
            }

            // Handle the error
            throw new Exception($"Google OAuth error: {response.StatusCode} {response.ReasonPhrase}");
        }

and then:

                    var payload = await ValidateGoogleGISToken(accessToken);

instead.

Sorry I took so long to come back with answer. Pinging interested parties: @SilincaHoratiu18 @Emiliano-Bucci @GZWZC @bvodola @jimcapel @MomenSherif