3

I'm using the google-auth-library-nodejs library to integrate into a number of GMail accounts, to get lists of emails.

My process flow is simple:

1) Try to authorize the client, using this function:

function _authorise(mailBox, callback) {
  let auth = new googleAuth();

  let clientId = eval(`process.env.GMAIL_API_CLIENT_ID_${mailBox.toUpperCase()}`);
  let clientSecret = eval(`process.env.GMAIL_API_CLIENT_SECRET_${mailBox.toUpperCase()}`);
  let redirectUri = eval(`process.env.GMAIL_API_REDIRECT_URI_${mailBox.toUpperCase()}`);
  let tokenFile = process.env.GMAIL_API_TOKEN_PATH + mailBox.toLowerCase()+ process.env.GMAIL_API_TOKEN_BASE_FILE_NAME;

  let oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUri);
  fs.readFile(tokenFile, ((err, token) => {
    if (err) {
      _getNewToken(mailBox,oauth2Client,callback);
    } else {
      oauth2Client.credentials = JSON.parse(token);
      callback(oauth2Client);
    }
  }))
}

2) The method will check for existence of a token in a file. If the file is NOT found, the following functions will create the file:

function _getNewToken(mailBox, oauth2Client, callback) {
  var authUrl = oauth2Client.generateAuthUrl({
    access_type: 'offline',
    scope: process.env.GMAIL_API_SCOPES
  });
  console.log('To authorize this app, please use this url: ', authUrl);
  var rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
  });
  rl.question('Enter the code from that page here: ', ((code) => {
    rl.close();
    oauth2Client.getToken(code, function(err, token) {
      if (err) {
        console.log('Error while trying to retrieve access token', err);
        return;
      }
      oauth2Client.credentials = token;
      _storeToken(mailBox,token);
      callback(oauth2Client);
    });
  }));
}

function _storeToken(mailBox, token) {
  let tokenFile = process.env.GMAIL_API_TOKEN_PATH + mailBox.toLowerCase()+ process.env.GMAIL_API_TOKEN_BASE_FILE_NAME;
  fs.writeFile(tokenFile, JSON.stringify(token));
}

I am using https://www.googleapis.com/auth/gmail.readonly as the scopes.

Here's a sample of the file created:

{"access_token":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","token_type":"Bearer","refresh_token":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","expiry_date":1460509994081}

When processed, here's a sample of the auth object that is returned:

OAuth2Client {
  transporter: DefaultTransporter {},
  clientId_: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com',
  clientSecret_: 'xxxxxxxxxxxxxxxxxxxxxxxx',
  redirectUri_: 'urn:ietf:wg:oauth:2.0:oob',
  opts: {},
  credentials: {
access_token: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
     token_type: 'Bearer',
     refresh_token: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
     expiry_date: 1460509994081
  }
}

If I delete the file, and go through the manual consent process, then the authentication works 100%, until the token expires. After this, I get the "Invalid Credentials" message.

My assumption is that once the token expires, that the refresh token will be used to auto recreate the access token. Am I missing something?

go4cas
  • 1,171
  • 5
  • 14
  • 27
  • if you request access of the user and are getting a new refresh token are you saving it? You can only have 25 active refrestokens with google after that they stop working and you get invalid credentials. – Linda Lawton - DaImTo Apr 13 '16 at 07:17
  • @DaImTo, yeah i was suspecting it is the 25 limit. What I don't understand is exactly when is the refresh token re-issued. – go4cas Apr 13 '16 at 07:20
  • I have just refreshed the access token, then I am get a new access_token, a new refresh_token and a new expiry_date. The expiry date is valid for 1 hour. So, my assumption is that after the 1 hour expiry window, the refresh_token will be used to create a new access_token automatically. Is that correct? Or will the refresh_token ALSO be regenerated after 1 hour? – go4cas Apr 13 '16 at 07:29
  • refresh token is reissued when a user clicks accept to authentication. If the access token has expired then the refresh token will be used to get a new access token from the authentication servers. I am not familiar with the inner workings of the nod client library but I would assume that it should handle that all for you. – Linda Lawton - DaImTo Apr 13 '16 at 07:38
  • @DaImTo, this is a server to server app, i.e. my API connects to Google's Auth server, using the code above. The only times I had to accept authentication was when I had to go through the process by pasting the URL from Google in a browser and get the code back. I is quite possible that I may have exceeded the 25 limit during testing. Is there a way to reset these refresh tokens? – go4cas Apr 13 '16 at 07:44
  • https://accounts.google.com/o/oauth2/revoke?token={token} – Linda Lawton - DaImTo Apr 13 '16 at 07:58
  • @DaImTo, I am already saving it .... _storeToken method above. When you say "discard any you have saved", is there a way to do that from the Developer Console? – go4cas Apr 13 '16 at 08:43
  • @DaImTo, I have figured out why I'm getting the "Invalid Credentials". It;s actually the node package that I'm using to interact with GMail. The package only accepts the access_token. So, I need to figure out how to get a new access_token from the google-auth-library-nodejs, using the refresh_token. Once I get that, I'll be able to pass that into the node-gmail lib, an dit "should" just work – go4cas Apr 13 '16 at 11:32
  • @go4cas Did you find the solution for how to get new refresh token using the old refresh token? I am stuck now. refresh_token gereated after the first time authorizzation is used to obtain new bearer access token but dont know how to get again new refresh token? I dont want the user to authorize for the app again and again. – Biku7 Oct 28 '22 at 13:27

2 Answers2

7

Here is the updated solution to get Access Token with Refresh Token:

const { google } = require("googleapis");
const OAuth2 = google.auth.OAuth2;

const oauth2Client = new OAuth2(
  "xxxxxxxxx.apps.googleusercontent.com", // ClientID
  "xxxxxxx", // Client Secret
  "https://developers.google.com/oauthplayground" // Redirect URL
);

oauth2Client.setCredentials({
  refresh_token:
    "xxxxxxxx"
});

const accessToken = oauth2Client.getAccessToken();
msahin
  • 1,520
  • 1
  • 16
  • 22
  • on setCredentials, why you are not put access_token there? first time sign in isn't that we need access_token to access the resource? – bl4ckck Jan 25 '21 at 12:49
  • @bl4ckck access_token has expiry time that why, we have to use request_token to setCredentials – Saad Ahmed Mar 05 '21 at 06:51
  • This is what worked for me. DIdn't know that access token isn't required here. Thanks! – VPaul Oct 09 '21 at 00:56
5

Okay, so I have discovered the getAccessToken method, which will check the access_token, and use it, unless it has expired, in which case it will use the refresh_token to generate a new access_token.

go4cas
  • 1,171
  • 5
  • 14
  • 27
  • As a note for others, it seems `getAccessToken` is deprecated. I'd love to be wrong here, but seems to be the case. – alexbea May 08 '18 at 21:56
  • It's not deprecated. It's the other one I think called `refreshAccessToken` – VPaul Oct 09 '21 at 00:58
  • how to get new refresh_token using the old refresh_token? @VPaul – Biku7 Oct 28 '22 at 13:28
  • Although, I'm late to answer this but generally you get the new refresh token either using the old one or by using the client ID and token provided to you when onboarding their API for the first time. – VPaul Apr 19 '23 at 19:10