2
votes

I've been dealing all day with this problem and I just can't figure out what's wrong.

I have an application using the Google Api client for javascript which is working with no problems. Now I want to do something on the server side, so after researching for a bit, found that the way to go would be to use the token on the client side with setAccessToken method in the backend.

So I try sending my token object as JSON (using JSON.stringify(gapi.auth.getToken()) ) and once I try doing an API call on the backend that requires auth, I get the following error:

The OAuth 2.0 access token has expired, and a refresh token is not available. Refresh tokens are not returned for responses that were auto-approved.

So, a little puzzled, I try veryfing the token using curl on google's endpoint, which returns the following

{
    "issued_to": "client_id",
    "audience": "client_id",
    "user_id": "user_id",
    "scope": "https://www.googleapis.com/auth/youtube.readonly https://www.googleapis.com/auth/youtube.upload https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/plus.me",
    "expires_in": 1465,
    "email": "user_email",
    "verified_email": true,
    "access_type": "online"
}

So I know the token is fine and valid. The way my code is setup is as follows (redacted for:

<?php
// The token JSON is not inline, it comes from another source directly from the client side, but this is how it looks
$token_json = '{"state":"","access_token":"TOTALLY_VALID_ACCESS_TOKEN","token_type":"Bearer","expires_in":"3600","scope":"https://www.googleapis.com/auth/youtube.readonly https://www.googleapis.com/auth/youtube.upload https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/plus.me","client_id":"CLIENT_ID","g_user_cookie_policy":"single_host_origin","cookie_policy":"single_host_origin","response_type":"token","issued_at":"1415583001","expires_at":"1415586601","g-oauth-window":{},"status":{"google_logged_in":false,"signed_in":true,"method":"PROMPT"}}';

$OAUTH2_CLIENT_ID = 'CLIENT_ID';
$OAUTH2_CLIENT_SECRET = 'CLIENT_SECRET';

$client = new Google_Client();
$client->setClientId($OAUTH2_CLIENT_ID);
$client->setClientSecret($OAUTH2_CLIENT_SECRET);
$client->setAccessToken($token_json);

$youtube = new Google_Service_YouTube($client);

try{

    /* Random code goes here */

    // Auth Exception here.
    $insertRequest = $youtube->videos->insert("status,snippet", $video);

} catch (Google_Exception $e) {
    var_dump($e->getMessage());
}   

Do I need to set up offline access or something? I have the requirement that the login process must be in javascript, so no chance of recreating the login flow from the backend.

Is there anything I'm missing?

2

2 Answers

3
votes

Well, if someone else stumbles into this:

Apparently, sharing tokens directly is not the way to go, since the different API wrappers handle tokens differently. What you have to do is pass the one-time code to PHP and use the following to acquire an access token

    $client = new Google_Client();
    $client->setClientId($OAUTH2_CLIENT_ID);
    $client->setClientSecret($OAUTH2_CLIENT_SECRET);

    // Couldn't find this anywhere in the docs, but it's required. If you don't pass this, you will get an Origin Mismatch error.
    $client->setRedirectUri('postmessage'); 

    // Get this from javascript
    $client->authenticate($ONE_TIME_CODE);

    // Optionally, you might want to retrieve the token and save it somewhere else e.g. the session, as the code is only good for a single use.         
    $_SESSION['token'] = $client->getAccessToken(); 

Also, from JS you NEED to specify you require this one-time code besides the JS token, which I couldn't find documented anywhere else as well.

Example settings, for gapi.auth.authorize

{
        client_id: CLIENT_ID
        scope: APP_SCOPES
        cookie_policy: 'single_host_origin'
        response_type: 'code token'
        immediate: true
}
0
votes

Thanks a lot, Moustached. I've also spent the whole day trying to solve that. However, I found not that obvious what is one-time code and how to get it, so I decided to provide my code here in case someone else face the same problem:

<html>
    <head>
        <script src="//ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
        <script src="https://apis.google.com/js/platform.js?onload=onGoogleAPILoad" async defer></script>
        <script>
            $(document).ready(function() {
                var auth2;

                window.onGoogleAPILoad = function() {
                    gapi.load('auth2', function() {
                        auth2 = gapi.auth2.init({ client_id: 'CLIENT_ID', scope: 'profile' });
                    });
                };

                $('button').on('click', function() {
                    auth2.grantOfflineAccess({'redirect_uri': 'postmessage'}).then(function(response) {
                        $.ajax({
                            method: 'POST',
                            url: '/auth/signin',
                            success: onAuthSuccess = function(response) {
                                // check response from your server and reload page if succeed
                            },
                            data: { code: response.code }
                        });
                    });
                });
            });
        </script>
    </head>
    <body>
        <button>Sign In</button>
    </body>
</html>

and for back-end the code is almost the same:

$client = new Google_Client();
$client->setClientId('CLIENT_ID');
$client->setClientSecret('CLIENT_SECRET');
$client->setRedirectUri('postmessage'); 

$client->authenticate($code); // here is one-time code

$plus = new Google_Service_Plus($client);
$user = $plus->people->get('me');
// $user is in array, containing now id, name, etc.

Here is an article on that topic.