1
votes

someone who's managed to maintain an OAuth2 refresh_token for google api before, could I compare code with you? The docs say that if I create an access_token with access_type:"offline", the client api will automatically use the refresh_token before expiry, but in my case this happens only once, at which time the refresh_token disappears from the updated token object. My code's as simple as this:

async function google(request)
{let [{google},keys]=await Promise.all(
[import("./node_modules/googleapis/build/src/index.js")
,import("./keys.json").then(json=>json.default)
]);
 let {client,secret,token}=keys;
 let authority=new google.auth.OAuth2(client,secret,request.url);
 authority.on('tokens',token=>save("keys.json",{...keys,token}).then(console.log));
 if(!token)
 if(!request.query.code)
 return authority.generateAuthUrl({scope,access_type:"offline",prompt:"consent"});
 else return new Promise(resolve=>
 authority.getToken(request.query.code,resolve)).then(fail=>fail||"new access_token");
 authority.setCredentials(token);
 google=google.sheets({version:"v4",auth:authority});
 return new Promise(resolve=>
 google.spreadsheets.values.get(sheet,resolve)).then(fail=>fail||"valid token");
 // first return value: [consent url redirecting to the same endpoint]
 // after using the url: "new access_token"
 // during the next 2 hours: "valid token" ("refresh_token" gone missing from keys.json in the second hour)
 // after 2 hours: "Error: missing required parameter: refresh_token"
}

could it be because I'm dynamically reinstantiating the API on each request? I'm setting the credentials each time too, so it shouldn't make a difference to having a static API

2

2 Answers

0
votes

This is the best example i have on hand it uses a different API but should give you an idea.

The library does not do the saving or the loading for you. However it does support you loading the refresh token in to it. So as long as you save it some where and then load it at the correct time it will work.

Check how i use TOKEN_PATH this is the path to where the tokens will be stored. When the code first runs the system will try to read that file and load any tokens stored in it.

NOTE refresh tokens are only returned to you the first time if you want it you need to save it. And not accidentally over write it with null.

There is a lot of information in the Read me on how this is all set up. Google api node.js client

const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
const dotenv = require('dotenv');
dotenv.config();
const CRED_FILE_PATH = process.env.API_CREDS;
console.log(`Your cred path is ${CRED_FILE_PATH}`); // ../creds.json
const VIEW_ID = process.env.VIEW_ID;
console.log(`Your view id is ${VIEW_ID}`);

// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/analytics.readonly'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = 'token.json';

// Load client secrets from a local file.
fs.readFile(CRED_FILE_PATH, (err, content) => {
    if (err) return console.log('Error loading client secret file:', err);
    // Authorize a client with credentials, then call the Google Drive API.
    authorize(JSON.parse(content), getUserReport);
});

/**
 * Create an OAuth2 client with the given credentials, and then execute the
 * given callback function.
 * @param {Object} credentials The authorization client credentials.
 * @param {function} callback The callback to call with the authorized client.
 */
function authorize(credentials, callback) {
    const {client_secret, client_id, redirect_uris} = credentials.installed;
    const oAuth2Client = new google.auth.OAuth2(
        client_id, client_secret, redirect_uris[0]);

    // Check if we have previously stored a token.
    fs.readFile(TOKEN_PATH, (err, token) => {
        if (err) return getAccessToken(oAuth2Client, callback);
        oAuth2Client.setCredentials(JSON.parse(token));
        callback(oAuth2Client);
    });
}

/**
 * Get and store new token after prompting for user authorization, and then
 * execute the given callback with the authorized OAuth2 client.
 * @param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
 * @param {getEventsCallback} callback The callback for the authorized client.
 */
function getAccessToken(oAuth2Client, callback) {
    const authUrl = oAuth2Client.generateAuthUrl({
        access_type: 'offline',
        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 retrieving 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);
        });
    });
}
0
votes

My realisation commenting on DalmTo's answer proved to be the answer: the refresh_token for an access_token is static, so the issue is when saving the new token, the refresh_token has to be preserved. This might be useful to mention in the docs as well, unless it is, and I just missed this detail.