378

I want to get the access token from Google. The Google API says that to get the access token, send the code and other parameters to token generating page, and the response will be a JSON Object like :

{
"access_token" : "ya29.AHES6ZTtm7SuokEB-RGtbBty9IIlNiP9-eNMMQKtXdMP3sfjL1Fc",
"token_type" : "Bearer",
"expires_in" : 3600,
"refresh_token" : "1/HKSmLFXzqP0leUihZp2xUt3-5wkU7Gmu2Os_eBnzw74"
}

However, I'm not receiving the refresh token. The response in my case is:

{
 "access_token" : "ya29.sddsdsdsdsds_h9v_nF0IR7XcwDK8XFB2EbvtxmgvB-4oZ8oU",
"token_type" : "Bearer",
"expires_in" : 3600
}
m0meni
  • 16,006
  • 16
  • 82
  • 141
Muhammad Usman
  • 10,426
  • 22
  • 72
  • 107

20 Answers20

941

The refresh_token is only provided on the first authorization from the user. Subsequent authorizations, such as the kind you make while testing an OAuth2 integration, will not return the refresh_token again. :)

  1. Go to the page showing Apps with access to your account: https://myaccount.google.com/u/0/permissions.
  2. Under the Third-party apps menu, choose your app.
  3. Click Remove access and then click Ok to confirm
  4. The next OAuth2 request you make will return a refresh_token (providing that it also includes the 'access_type=offline' query parameter.

Alternatively, you can add the query parameters prompt=consent&access_type=offline to the OAuth redirect (see Google's OAuth 2.0 for Web Server Applications page).

This will prompt the user to authorize the application again and will always return a refresh_token.

glidester
  • 599
  • 6
  • 23
Rich Sutton
  • 10,004
  • 1
  • 17
  • 21
  • 25
    This didn't work for me, but adding the param "access_type=offline" seemed to do the trick: https://developers.google.com/accounts/docs/OAuth2WebServer#offline – Jesse Sep 28 '12 at 23:24
  • 98
    You need `access_type=offline` in all cases when you want the `refresh_token`. – DanH Jan 16 '13 at 03:07
  • 8
    But how do i refresh the token after its expiry in this case? – vivek_jonam Feb 25 '13 at 10:59
  • 6
    @vivek_jonam Store the refresh token and expiry date. When it expires you request a new token using the refresh token. See here: https://developers.google.com/accounts/docs/OAuth2WebServer#refresh – gelviis Jun 03 '13 at 13:18
  • Rich do you mean you add approval_prompt=force to the OAuth redirect URL?? – Supertecnoboff Feb 08 '14 at 14:41
  • @Supertecnoboff Yes, but keep in mind that doing that will always prompt the user to authorize your app and always return a refresh token. If you just want to silently get a new token after it expires, follow Gregor's instructions. – Rich Sutton Feb 09 '14 at 17:28
  • 4
    I got it working with `$client->setAccessType('offline')`. The `function setApprovalPrompt()` is already passed in `force`, by default. – moey Jan 20 '15 at 06:27
  • I have a CMS where different users use different google accounts to connect to the analytics api. However, sometimes several users can connect using the same corporate google account, but each wanting access to a different Analytics accounts. Only the first one receives the refresh token, while all others don't and thus have to reconnect every hour. Isn't there a way to get the SAME refresh token for subsequent authentications instead of just the access_token which expires within the hour? – Costin_T Jun 11 '15 at 14:39
  • I couldn't figure out how to revoke the token at the link you provided above. However, I did find another link: https://security.google.com/settings/security/permissions?pli=1 -- Perhaps they moved it? – mgilson Oct 21 '15 at 21:51
  • 4
    If you can't get it to work, remember that `access_type=offline` is a FRONT END option that is passed when the client requests the authorization code, NOT a back end option when the server exchanges that code for an access_token. The user gives the server permission to refresh the token indefinitely by granting offline access. More information here with URLs you can try in curl: https://developers.google.com/identity/protocols/OAuth2WebServer – Zack Morris Dec 12 '15 at 02:36
  • Also if you use Satellizer, here is how to add offline access to $authProvider.google in AngularJS: https://github.com/sahat/satellizer/issues/205 (just missed Stack Overflow's 5 minute editing window). – Zack Morris Dec 12 '15 at 02:46
  • 3
    Edited answer to something that is working as this answer is old and no longer seems to work. I spent significant time trying this as there are multiple places saying to use this method. I finally read the documentation (which I should have done first) and it says you must use ```prompt=consent```. Reference: https://developers.google.com/identity/protocols/OAuth2WebServer#offline – Goblinlord Jan 05 '16 at 00:50
  • I could not find "Revoke Token" option. Could you tell me where it is exactly.? – Harish Mahajan Jul 21 '16 at 06:11
  • if you have both `approval_prompt=force` and `prompt=consent` you'll get `Conflict params: approval_prompt and prompt` – Anand Rockzz Aug 30 '17 at 14:38
  • The URL should be https://myaccount.google.com/u/0/security to get to the first logged in user. – Tri Nguyen Oct 06 '17 at 18:41
  • 1
    FYI, agree with the "needs approval prompt", the JS API syntax that got me working was: `currentUser.grantOfflineAccess({ prompt:'consent', scope:'...' })` – Jeff Ward Jan 12 '18 at 16:52
  • Very helpful, thanks! Setting `access_type=offline` enables the receiving of `refresh_tokens`, but only for new authentications. For already existing old grants, it won't work unless you perform this important step and remove them. – Prahlad Yeri Jun 03 '18 at 17:02
  • Setting prompt: "consent" and access_type: "offline" returns a refresh_token every time for me, thank you. – Jordan Réjaud Aug 20 '19 at 03:15
  • The `prompt=consent&access_type=offline` thing helped, thanks. – snehanshu.js Feb 23 '20 at 10:16
  • prompt=consent&access_type=offline also worked for me thank you :) – Ganesh Gudghe Nov 12 '20 at 07:22
  • I added access_type=offline and still not getting the refresh_token, so i tried the solution above and it worked. keep in mine you still need to add access_type=offline. – momouu Jul 14 '21 at 22:31
  • How to do this using Android GoogleSignInOptions ? I don't see any option to set prompt=consent – K Pradeep Kumar Reddy Jul 13 '22 at 17:53
  • Did all that but didn't get a param named `refresh_token`, there is however a param name `code`, is it same as refresh token? – Salim Shamim Jul 21 '22 at 11:42
70

In order to get the refresh token you have to add both approval_prompt=force and access_type="offline" If you are using the java client provided by Google it will look like this:

GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
            HTTP_TRANSPORT, JSON_FACTORY, getClientSecrets(), scopes)
            .build();

AuthorizationCodeRequestUrl authorizationUrl =
            flow.newAuthorizationUrl().setRedirectUri(callBackUrl)
                    .setApprovalPrompt("force")
                    .setAccessType("offline");
Prasad Jadhav
  • 5,090
  • 16
  • 62
  • 80
Gal Morad
  • 806
  • 7
  • 7
  • 1
    In node: var authUrl = oauth2Client.generateAuthUrl({ access_type: 'offline', scope: SCOPES, approval_prompt:'force' }); – Joris Mans May 03 '17 at 09:07
  • 5
    It's outrageous that google has not addressed this in their documentation or at least not in the php or oath2 documentation that i've been staring at for 7 hours. Why in the world is this not in big bold text in their docs – Colin Rickels Nov 15 '17 at 17:15
  • Thank you! Docs here (https://github.com/googlesamples/apps-script-oauth2) are very misleading about this parameter. When I added approval_prompt=force I finally got a refresh token. – Alex Zhevzhik Nov 29 '17 at 15:12
  • 8
    approval_prompt=force did not work for me, but prompt=consent did. – Stefan Reich Aug 03 '20 at 11:26
55

I'd like to add a bit more info on this subject for those frustrated souls who encounter this issue. The key to getting a refresh token for an offline app is to make sure you are presenting the consent screen. The refresh_token is only returned immediately after a user grants authorization by clicking "Allow".

enter image description here

The issue came up for me (and I suspect many others) after I'd been doing some testing in a development environment and therefore already authorized my application on a given account. I then moved to production and attempted to authenticate again using an account which was already authorized. In this case, the consent screen will not come up again and the api will not return a new refresh token. To make this work, you must force the consent screen to appear again by either:

prompt=consent

or

approval_prompt=force

Either one will work but you should not use both. As of 2021, I'd recommend using prompt=consent since it replaces the older parameter approval_prompt and in some api versions, the latter was actually broken (https://github.com/googleapis/oauth2client/issues/453). Also, prompt is a space delimited list so you can set it as prompt=select_account%20consent if you want both.

Of course you also need:

access_type=offline

Additional reading:

  • To add, if you're using the GoLang oauth library, you can set this via the `oauth2.ApprovalForce` AuthCodeOption: https://pkg.go.dev/golang.org/x/oauth2#AuthCodeOption – Aaron Krauss Jan 20 '22 at 20:41
33

I searched a long night and this is doing the trick:

Modified user-example.php from admin-sdk

$client->setAccessType('offline');
$client->setApprovalPrompt('force');
$authUrl = $client->createAuthUrl();
echo "<a class='login' href='" . $authUrl . "'>Connect Me!</a>";

then you get the code at the redirect url and the authenticating with the code and getting the refresh token

$client()->authenticate($_GET['code']);
echo $client()->getRefreshToken();

You should store it now ;)

When your accesskey times out just do

$client->refreshToken($theRefreshTokenYouHadStored);
Norbert
  • 331
  • 3
  • 3
21

This has caused me some confusion so I thought I'd share what I've come to learn the hard way:

When you request access using the access_type=offline and approval_prompt=force parameters you should receive both an access token and a refresh token. The access token expires soon after you receive it and you will need to refresh it.

You correctly made the request to get a new access token and received the response that has your new access token. I was also confused by the fact that I didn't get a new refresh token. However, this is how it is meant to be since you can use the same refresh token over and over again.

I think some of the other answers assume that you wanted to get yourself a new refresh token for some reason and sugggested that you re-authorize the user but in actual fact, you don't need to since the refresh token you have will work until revoked by the user.

jeteon
  • 3,471
  • 27
  • 40
  • 1
    I have a CMS where different users use different google accounts to connect to the analytics api. However, sometimes several users can connect using the same corporate google account, but each wanting access to a different Analytics accounts. Only the first one receives the refresh token, while all others don't and thus have to reconnect every hour. Isn't there a way to get the SAME refresh token for subsequent authentications instead of just the access_token which expires within the hour? – Costin_T Jun 11 '15 at 14:40
  • 1
    The API seems to produce the **refresh** token exactly once. Any "sharing" of the token would have to happen in your code. You would have to be careful of not accidentally giving new access privileges to users though. A simple way to do this is to have the application keep track of **refresh** tokens and the associated accounts in their own storage (separate 'table' in SQLese). Then, when you want to get a new **access** token you check for and use this possibly common token from there. Implemented a certain way, your code doesn't need to know who actually got the token. – jeteon Jun 11 '15 at 16:15
  • 1
    I don't know how I could identify which refresh token I should associate with a new access token that I just got. There are different users who do the login, and the only thing they have in common is that they use the same Google account (e-mail) to connect to the API. But Google doesn't send back an ID of the account or the e-mail, it just sends back a token. So I don't know how to associate the 2 different CMS users... – Costin_T Jun 11 '15 at 22:17
  • I have fully explained my issue here: http://stackoverflow.com/questions/30217524/google-api-oauth2-only-one-refresh-token-for-all-users – Costin_T Jun 11 '15 at 22:18
  • Youtube oAuth2 refresh_token shown only when used force. – Dmitry Polushkin Feb 12 '16 at 11:57
9

Rich Sutton's answer finally worked for me, after I realized that adding access_type=offline is done on the front end client's request for an authorization code, not the back end request that exchanges that code for an access_token. I've added a comment to his answer and this link at Google for more info about refreshing tokens.

P.S. If you are using Satellizer, here is how to add that option to the $authProvider.google in AngularJS.

Community
  • 1
  • 1
Zack Morris
  • 4,727
  • 2
  • 55
  • 83
  • Very minor details but important one. Saved me ! Thanks :) – Dexter Mar 23 '16 at 18:12
  • @ZackMorris So.. do u mean to say that I cant get refresh_token from backend using access token? – Nevermore Apr 12 '16 at 05:53
  • @Nevermore You can't get a refresh_token from the access_token itself. If you want your server to handle refreshes, then you'll need to store the refresh_token in your database the first time. Also if you are doing a client OAuth flow on the front end, then users will have to send their refresh_token to the back end if they want the server to refresh for them. – Zack Morris Apr 13 '16 at 21:37
7

In order to get the refresh_token you need to include access_type=offline in the OAuth request URL. When a user authenticates for the first time you will get back a non-nil refresh_token as well as an access_token that expires.

If you have a situation where a user might re-authenticate an account you already have an authentication token for (like @SsjCosty mentions above), you need to get back information from Google on which account the token is for. To do that, add profile to your scopes. Using the OAuth2 Ruby gem, your final request might look something like this:

client = OAuth2::Client.new(
  ENV["GOOGLE_CLIENT_ID"],
  ENV["GOOGLE_CLIENT_SECRET"],
  authorize_url: "https://accounts.google.com/o/oauth2/auth",
  token_url: "https://accounts.google.com/o/oauth2/token"
)

# Configure authorization url
client.authorize_url(
  scope: "https://www.googleapis.com/auth/analytics.readonly profile",
  redirect_uri: callback_url,
  access_type: "offline",
  prompt: "select_account"
)

Note the scope has two space-delimited entries, one for read-only access to Google Analytics, and the other is just profile, which is an OpenID Connect standard.

This will result in Google providing an additional attribute called id_token in the get_token response. To get information out of the id_token, check out this page in the Google docs. There are a handful of Google-provided libraries that will validate and “decode” this for you (I used the Ruby google-id-token gem). Once you get it parsed, the sub parameter is effectively the unique Google account ID.

Worth noting, if you change the scope, you'll get back a refresh token again for users that have already authenticated with the original scope. This is useful if, say, you have a bunch of users already and don't want to make them all un-auth the app in Google.

Oh, and one final note: you don't need prompt=select_account, but it's useful if you have a situation where your users might want to authenticate with more than one Google account (i.e., you're not using this for sign-in / authentication).

coreyward
  • 77,547
  • 20
  • 137
  • 166
  • I think the part about identifying users without storing any personal information is key. Thanks for pointing it out, I didn't saw any reference on google docs about that. – Danielo515 Sep 20 '19 at 11:32
3

1. How to get 'refresh_token' ?

Solution: access_type='offline' option should be used when generating authURL. source : Using OAuth 2.0 for Web Server Applications

2. But even with 'access_type=offline', I am not getting the 'refresh_token' ?

Solution: Please note that you will get it only on the first request, so if you are storing it somewhere and there is a provision to overwrite this in your code when getting new access_token after previous expires, then make sure not to overwrite this value.

From Google Auth Doc : (this value = access_type)

This value instructs the Google authorization server to return a refresh token and an access token the first time that your application exchanges an authorization code for tokens.

If you need 'refresh_token' again, then you need to remove access for your app as by following the steps written in Rich Sutton's answer.

3

I'm using nodejs client for access to private data

The solution was add the promp property with value consent to the settings object in oAuth2Client.generateAuthUrl function. Here is my code:

const getNewToken = (oAuth2Client, callback) => {
    const authUrl = oAuth2Client.generateAuthUrl({
        access_type: 'offline',
        prompt: 'consent',
        scope: SCOPES,
    })
    console.log('Authorize this app by visiting this url:', authUrl)
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
    })
    rl.question('Enter the code from that page here: ', (code) => {
        rl.close()
        oAuth2Client.getToken(code, (err, token) => {
            if (err) return console.error('Error while trying to retrieve access token', err)
            oAuth2Client.setCredentials(token)
            // Store the token to disk for later program executions
            fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
                if (err) return console.error(err)
                console.log('Token stored to', TOKEN_PATH)
            })
            callback(oAuth2Client)
        })
    })
}

You can use the online parameters extractor to get the code for generate your token:

Online parameters extractor

Here is the complete code from google official docs:

https://developers.google.com/sheets/api/quickstart/nodejs

I hope the information is useful

jarraga
  • 392
  • 4
  • 7
  • This seems to be a proper way. This works perfectly after user has revoked access from Google Account Settings and if you call "oAuth2Client.revokeToken" (which you should call if you app supports "unlinking") – arjavlad May 12 '22 at 12:21
2

Setting this will cause the refresh token to be sent every time:

$client->setApprovalPrompt('force');

an example is given below (php):

$client = new Google_Client();
$client->setClientId($client_id);
$client->setClientSecret($client_secret);
$client->setRedirectUri($redirect_uri);
$client->addScope("email");
$client->addScope("profile"); 
$client->setAccessType('offline');
$client->setApprovalPrompt('force');
apadana
  • 13,456
  • 15
  • 82
  • 98
2
  • To get a refresh token using postman, here is an example of the configurations

enter image description here

  • Expected Response

enter image description here

1

For me I was trying out CalendarSampleServlet provided by Google. After 1 hour the access_key times out and there is a redirect to a 401 page. I tried all the above options but they didn't work. Finally upon checking the source code for 'AbstractAuthorizationCodeServlet', I could see that redirection would be disabled if credentials are present, but ideally it should have checked for refresh token!=null. I added below code to CalendarSampleServlet and it worked after that. Great relief after so many hours of frustration . Thank God.

if (credential.getRefreshToken() == null) {
    AuthorizationCodeRequestUrl authorizationUrl = authFlow.newAuthorizationUrl();
    authorizationUrl.setRedirectUri(getRedirectUri(req));
    onAuthorization(req, resp, authorizationUrl);
    credential = null;
}
jeteon
  • 3,471
  • 27
  • 40
Anoop Isaac
  • 932
  • 12
  • 15
1

Using offline access and prompt:consent worked well to me:

   auth2 = gapi.auth2.init({
                    client_id: '{cliend_id}' 
   });

   auth2.grantOfflineAccess({prompt:'consent'}).then(signInCallback); 
1

In order to get new refresh_token each time on authentication the type of OAuth 2.0 credentials created in the dashboard should be "Other". Also as mentioned above the access_type='offline' option should be used when generating the authURL.

When using credentials with type "Web application" no combination of prompt/approval_prompt variables will work - you will still get the refresh_token only on the first request.

1

#March - 2023

Short answer -->

access_type: 'offline',
prompt: 'consent',

pass these in  oAuth2Client.generateAuthUrl({})
0

now google had refused those parameters in my request (access_type, prompt)... :( and there is no "Revoke Access" button at all. I'm frustrating because of getting back my refresh_token lol

UPDATE: I found the answer in here :D you can get back the refresh token by a request https://developers.google.com/identity/protocols/OAuth2WebServer

curl -H "Content-type:application/x-www-form-urlencoded" \ https://accounts.google.com/o/oauth2/revoke?token={token}

The token can be an access token or a refresh token. If the token is an access token and it has a corresponding refresh token, the refresh token will also be revoked.

If the revocation is successfully processed, then the status code of the response is 200. For error conditions, a status code 400 is returned along with an error code.

0
    #!/usr/bin/env perl

    use strict;
    use warnings;
    use 5.010_000;
    use utf8;
    binmode STDOUT, ":encoding(utf8)";

    use Text::CSV_XS;
    use FindBin;
    use lib $FindBin::Bin . '/../lib';
    use Net::Google::Spreadsheets::V4;

    use Net::Google::DataAPI::Auth::OAuth2;

    use lib 'lib';
    use Term::Prompt;
    use Net::Google::DataAPI::Auth::OAuth2;
    use Net::Google::Spreadsheets;
    use Data::Printer ;


    my $oauth2 = Net::Google::DataAPI::Auth::OAuth2->new(
         client_id => $ENV{CLIENT_ID},
         client_secret => $ENV{CLIENT_SECRET},
         scope => ['https://www.googleapis.com/auth/spreadsheets'],
    );
    my $url = $oauth2->authorize_url();
    # system("open '$url'");
    print "go to the following url with your browser \n" ;
    print "$url\n" ;
    my $code = prompt('x', 'paste code: ', '', '');
    my $objToken = $oauth2->get_access_token($code);

    my $refresh_token = $objToken->refresh_token() ;

    print "my refresh token is : \n" ;
    # debug p($refresh_token ) ;
    p ( $objToken ) ;


    my $gs = Net::Google::Spreadsheets::V4->new(
            client_id      => $ENV{CLIENT_ID}
         , client_secret  => $ENV{CLIENT_SECRET}
         , refresh_token  => $refresh_token
         , spreadsheet_id => '1hGNULaWpYwtnMDDPPkZT73zLGDUgv5blwJtK7hAiVIU'
    );

    my($content, $res);

    my $title = 'My foobar sheet';

    my $sheet = $gs->get_sheet(title => $title);

    # create a sheet if does not exit
    unless ($sheet) {
         ($content, $res) = $gs->request(
              POST => ':batchUpdate',
              {
                    requests => [
                         {
                              addSheet => {
                                    properties => {
                                         title => $title,
                                         index => 0,
                                    },
                              },
                         },
                    ],
              },
         );

         $sheet = $content->{replies}[0]{addSheet};
    }

    my $sheet_prop = $sheet->{properties};

    # clear all cells
    $gs->clear_sheet(sheet_id => $sheet_prop->{sheetId});

    # import data
    my @requests = ();
    my $idx = 0;

    my @rows = (
         [qw(name age favorite)], # header
         [qw(tarou 31 curry)],
         [qw(jirou 18 gyoza)],
         [qw(saburou 27 ramen)],
    );

    for my $row (@rows) {
         push @requests, {
              pasteData => {
                    coordinate => {
                         sheetId     => $sheet_prop->{sheetId},
                         rowIndex    => $idx++,
                         columnIndex => 0,
                    },
                    data => $gs->to_csv(@$row),
                    type => 'PASTE_NORMAL',
                    delimiter => ',',
              },
         };
    }

    # format a header row
    push @requests, {
         repeatCell => {
              range => {
                    sheetId       => $sheet_prop->{sheetId},
                    startRowIndex => 0,
                    endRowIndex   => 1,
              },
              cell => {
                    userEnteredFormat => {
                         backgroundColor => {
                              red   => 0.0,
                              green => 0.0,
                              blue  => 0.0,
                         },
                         horizontalAlignment => 'CENTER',
                         textFormat => {
                              foregroundColor => {
                                    red   => 1.0,
                                    green => 1.0,
                                    blue  => 1.0
                              },
                              bold => \1,
                         },
                    },
              },
              fields => 'userEnteredFormat(backgroundColor,textFormat,horizontalAlignment)',
         },
    };

    ($content, $res) = $gs->request(
         POST => ':batchUpdate',
         {
              requests => \@requests,
         },
    );

    exit;

    #Google Sheets API, v4

    # Scopes
    # https://www.googleapis.com/auth/drive   View and manage the files in your Google D# # i# rive
    # https://www.googleapis.com/auth/drive.file View and manage Google Drive files and folders that you have opened or created with this app
    # https://www.googleapis.com/auth/drive.readonly   View the files in your Google Drive
    # https://www.googleapis.com/auth/spreadsheets  View and manage your spreadsheets in Google Drive
    # https://www.googleapis.com/auth/spreadsheets.readonly  View your Google Spreadsheets
Yordan Georgiev
  • 5,114
  • 1
  • 56
  • 53
0

My solution was a bit weird..i tried every solution i found on internet and nothing. Surprisely this worked: delete the credentials.json, refresh, vinculate your app in your account again. The new credentials.json file will have the refresh token. Backup this file somewhere. Then keep using your app until the refresh token error comes again. Delete the crendetials.json file that now is only with an error message (this hapenned in my case), then paste you old credentials file in the folder, its done! Its been 1 week since ive done this and had no more problems.

0

Adding access_type=offline to the authorisation Google authorisation URL did the trick for me. I am using Java and Spring framework.

This is the code that creates the client registration:

return CommonOAuth2Provider.GOOGLE
                    .getBuilder(client)
                    .scope("openid", "profile", "email", "https://www.googleapis.com/auth/gmail.send")
                    .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                    .authorizationUri("https://accounts.google.com/o/oauth2/v2/auth?access_type=offline")
                    .clientId(clientId)
                    .redirectUriTemplate("{baseUrl}/{action}/oauth2/code/{registrationId}")
                    .clientSecret(clientSecret)
                    .build();

The important part here is the authorization URI, to which ?access_type=offline is appended.

gil.fernandes
  • 12,978
  • 5
  • 63
  • 76
0

In my case I followed all the above steps and still had the same problem.

I'm using python and was trying to get the credentials as follows(doc ref to why this should work.):

creds = {
          'token': credentials.token,
          'refresh_token': credentials.refresh_token,
          'token_uri': credentials.token_uri,
          'client_id': credentials.client_id,
          'client_secret': credentials.client_secret,
          'scopes': credentials.scopes
}

which for whatever reason was returning everything correctly except the refresh token

changing it to the below worked in the end.

import json
creds = json.loads(credentials.to_json())
Daniel Olson
  • 73
  • 1
  • 3