how to refresh token on google oauth2 using firebase functions?

Viewed 1134

I developed an integration using Google Oauth2 inside firebase functions to access Google Sheets API. The integration works correctly but I'm having problems to make sure the refresh token is running correctly. The function stops working after the first token expires.

when this happens the following error occur:


Function execution started
Error: No refresh token is set.
    at OAuth2Client.refreshTokenNoCache (/workspace/node_modules/googleapis-common/node_modules/google-auth-library/build/src/auth/oauth2client.js:161:19)
    at OAuth2Client.refreshToken (/workspace/node_modules/googleapis-common/node_modules/google-auth-library/build/src/auth/oauth2client.js:142:25)
    at OAuth2Client.getRequestMetadataAsync (/workspace/node_modules/googleapis-common/node_modules/google-auth-library/build/src/auth/oauth2client.js:256:28)
    at OAuth2Client.requestAsync (/workspace/node_modules/googleapis-common/node_modules/google-auth-library/build/src/auth/oauth2client.js:329:34)
    at OAuth2Client.request (/workspace/node_modules/googleapis-common/node_modules/google-auth-library/build/src/auth/oauth2client.js:323:25)
    at createAPIRequestAsync (/workspace/node_modules/googleapis-common/build/src/apirequest.js:292:27)
    at Object.createAPIRequest (/workspace/node_modules/googleapis-common/build/src/apirequest.js:43:9)
    at Resource$Spreadsheets$Values.update (/workspace/node_modules/googleapis/build/src/apis/sheets/v4.js:601:37)
    at exports.loadStripeData.functions.runWith.https.onRequest (/workspace/index.js:176:32)
    at process._tickCallback (internal/process/next_tick.js:68:7)

I want to make sure the token refresh correctly and get stored on Firestore.

What am I doing wrong?

index.js:

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const {google} = require('googleapis');

const sheets = google.sheets('v4');
admin.initializeApp();

const CLIENT_ID     = 'CLIENT_ID';
const CLIENT_SECRET = 'CLIENT_SECRETT';
const REDIRECT_URL  = 'https://us-central1-MY-PROJECT.cloudfunctions.net/oauth2callback';
const SCOPES        = ['https://www.googleapis.com/auth/spreadsheets'];

oauth2Client.on('tokens', (tokens) => {
  if (tokens.refresh_token) {
    try {
      admin.firestore()
        .collection('oauth2')
        .doc('google')
        .set({
          tokens: tokens.refresh_token,
        });
    } catch (error) {
      console.error(JSON.stringify(error));
    }
  }
});

/*asks user permission to access his spreadsheets*/
exports.authenticate = functions.https.onRequest((req, res) => {
  const authorizeUrl = oauth2Client.generateAuthUrl({
    access_type: 'offline',
    scope: SCOPES.join(','),
  });

  res.send(`<html>click here: <a href="${authorizeUrl}">${authorizeUrl}</a></html>`)
});

/*callback function for when the user finishes authenticating*/
exports.oauth2callback = functions.https.onRequest(async(req, res) => {
  const code = req.query.code.toString() || '';

  try {
    await admin.firestore()
      .collection('oauth2')
      .doc('google')
      .set({
        code: decodeURIComponent(code)
      });
  } catch(error) {
    res.send(JSON.stringify(error))
  }

  res.send('auth successfully. You can close this tab');
});

/* get token from Firestone to execute function*/
async function oauth2Auth() {
  const doc = await admin.firestore()
    .collection('oauth2')
    .doc('google')
    .get();
  const credentials = doc.data();

  if (credentials.code !== undefined) {
    const response = await oauth2Client.getToken(credentials.code);
    credentials.tokens = response.tokens;
    delete credentials.code;

    try {
      await admin.firestore()
        .collection('oauth2')
        .doc('google')
        .set({
          tokens: credentials.tokens,
        })
    } catch (error) {
      console.error(error);
    }
  }
  oauth2Client.setCredentials(credentials.tokens);
}

/*function that requires google sheets api*/
exports.mainFunction = functions.https.onRequest(async(req, res) => {
  oauth2Auth();
  //do main function
});
1 Answers

Finally discovered the problem!

You only get the refreshing token in the first time you ask for authorization. So if you're don't save it correctly you have to ask permission again.

To solve it:

when redirecting the user to the authorization URL add the following parameters to have sure you get the refreshing token:

access_type=offline&prompt=consent

to save the refreshing token:

oauth2Client.on('tokens', async(tokens:any) => {
    if (tokens.refresh_token) {
      try {
        const authorization = await oauth2Client.getToken(tokens.refresh_token);

        await admin.firestore()
          .collection('collectionName')
          .doc(docId)
          .update({
            token: authorization.tokens
          })
          
      } catch (error) {
        console.error(JSON.stringify(error));
      }
    }
  });
Related