access-tokengoogle-signinspring-socialspring-social-google

Use token received from JavaScript client to create a social connection on the server


I have a webpage using PHP/JavaScript that can successfully do the Google Signin process on the client side. I would like to then forward the obtained token to the backend which uses Spring-Social to create a social connection and be able to call on the APIs to which the app has been authorized. I managed to do exactly this with both Facebook (OAUTH2) and Twitter (OAUTH1) both working flawlessly but for Google I always get a 401 response when trying to create the connection. The code in the server is like this:

AccessGrant accessGrant = new AccessGrant(accessToken);
connection = ((OAuth2ConnectionFactory<?>) connectionFactory).createConnection(accessGrant);

The code in the client something like this:

    function onSignIn(googleUser) {
        var profile = googleUser.getBasicProfile();
        console.log('ID: ' + profile.getId());
        console.log('Name: ' + profile.getName());
        console.log('Email: ' + profile.getEmail());

        var access_token = googleUser.getAuthResponse().id_token;
        var xhr = new XMLHttpRequest();
        xhr.open('POST', 'http://localhost/signin/google');
        xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
        xhr.onload = function() {
            console.log('Signed in as: ' + xhr.responseText);
            console.log('access_token=' + access_token);
        };
        xhr.send('access_token=' + access_token);

    }

Most of the latter is copied from the Google documentation on how to authenticate with a backend server found here: https://developers.google.com/identity/sign-in/web/backend-auth

I've traced the calls inside spring-social-google and they lead to a REST call to https://www.googleapis.com/oauth2/v2/userinfo which is the one that replies 401 and the following JSON:

{
    "error": {
        "errors": [{
            "domain": "global",
            "reason": "authError",
            "message": "Invalid Credentials",
            "locationType": "header",
            "location": "Authorization"
        }],
        "code": 401,
        "message": "Invalid Credentials"
    }
}

I've tried to call on this same REST API myself and I also get the same response, so it does not seem to be an issue of spring-social-google not sending the token properly as I've read on some other posts.

I believe that the issue is somehow related to the fact that the JavaScript API is giving me an "id_token" and the REST API is expecting an "access_token". However, I am not finding a way to obtain the access token as something separate from the id_token and the Google documentation does send the id_token to the backend. There is an "access_token" property alongside the used "id_token" property in googleUser.getAuthResponse() but it is coming back undefined. This doc is of course not aimed at spring-social so I am not saying it is incorrect, I am just left wondering what is the proper way to achieve this.

Is spring-social at fault by not being able to deal with the id_token?

Am I at fault not seeing some clear way to get the access_token?

Anyway, I feel like I am somehow close but still the solution seems well out of grasp.

Thank you!


Solution

  • Well, I found it. It seems that writing the whole thing down really tripped something in my brain...

    The issue is in fact that the id_token cannot take the place of the access_token. To my taste, Google documentation could be a little more explicit about this in the example they show but they do end up explaining it in the actual client reference at https://developers.google.com/identity/sign-in/web/reference

    The key is the boolean parameter to getAuthResponse() which is optional and defaults to false. From the doc:

    includeAuthorizationData Optional: A boolean that specifies whether to always return an access token and scopes. By default, the access token and requested scopes are not returned when fetch_basic_profile is true (the default value) and no additional scopes are requested.

    By setting this to true in my JS the access_token field which was previously undefined was populated and with this token one can access the /oauth2/v2/userinfo endpoint and spring-social-google can correctly create a connection just like the other providers.

    I hope this helps someone else at least.