googleapis version: 59.0.0I expect the API to automatically refresh the access token on the next API call after it expires, but I only get an "Invalid Client" error. I can successfully access the API and upload/download google drive files until the access token expires, so I know the tokens I received from the API on first signin are valid. I have that same refresh token assigned to the oauth2client immediately before any request. I can verify that they are set via console.log. Is there some missing step to get the API to "automatically refresh the access token" as it says in the documentation?
oAuth2Client.setCredentials({
access_token: googleAccessToken,
refresh_token: googleRefreshToken
});
馃憢 The invalid client error sounds suspect. Can you share:
GoogleAuth client or OAuth2ClientSure thing. Let me know if you need more details:
The code for my OAuth2Client:
let oAuth2Client = new google.auth.OAuth2(
config.googleClientId,
config.googleClientSecret,
'/auth/google/redirect'
);
oAuth2Client.on('tokens', (tokens) => {
console.log("ON TOKENS"); //<-- This is never reached
if (tokens.refresh_token) {
// store the refresh_token in my database!
console.log("Refresh Token: " + tokens.refresh_token);
}
console.log("New Access Token: " + tokens.access_token);
});
.... later in the same function....
oAuth2Client.setCredentials({
access_token: account.googleAccessToken,
refresh_token: account.googleRefreshToken
});
console.log("Access Token: " + account.googleAccountToken); //Works fine
console.log("Refresh Token: " + account.googleRefreshToken); //Works fine
const drive = google.drive({version: 'v3', auth: oAuth2Client});
fileMetadata = {
'name': brew.title + '.txt',
'appProperties': {
'shareId': brew.shareId,
'editId' : brew.editId,
'title' : brew.title,
}
};
media = {
mimeType: 'text/plain',
body: brew.text
};
drive.files.create({
resource: fileMetadata,
media: media
})
.then(res => {
console.log("Success!"); // Only reaches here on a fresh login
console.log(res.data.id);
return res.status(200).send(res.data);
})
.catch(err => {
console.log("WHYYYYYY"); // Reaches here after 1 hour. Refresh token not working????!?!
console.error(err);
//return res.status(500).send('Error while saving');
});
Here's the full error I get:
WHYYYYYYY
GaxiosError: Invalid Credentials
at Gaxios._request (C:\Users\thato\Documents\homebrewery\node_modules\gaxios\build\src\gaxios.js:89:23)
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at async OAuth2Client.requestAsync (C:\Users\thato\Documents\homebrewery\node_modules\google-auth-library\build\src\auth\oauth2client.js:343:18) {
response: {
config: {
url: 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart',
method: 'POST',
userAgentDirectives: [Array],
paramsSerializer: [Function],
data: [PassThrough],
headers: [Object],
params: [Object: null prototype],
validateStatus: [Function],
retry: true,
body: [PassThrough],
responseType: 'json',
retryConfig: [Object]
},
data: { error: [Object] },
headers: {
'alt-svc': 'h3-29=":443"; ma=2592000,h3-27=":443"; ma=2592000,h3-T051=":443"; ma=2592000,h3-T050=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"',
connection: 'close',
'content-length': '249',
'content-type': 'application/json; charset=UTF-8',
date: 'Fri, 04 Sep 2020 01:46:29 GMT',
server: 'UploadServer',
vary: 'Origin, X-Origin',
'www-authenticate': 'Bearer realm="https://accounts.google.com/", error=invalid_token',
'x-guploader-uploadid': 'ABg5-UxxOru2I9OyxWCFXebrqdW8XnV-FOL2kXiyEUdhUuEMY9mlCiQfOB19355LDnUVa1EkMdHWgj4X60LS0IX-ylk'
},
status: 401,
statusText: 'Unauthorized',
request: {
responseURL: 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart'
}
},
config: {
url: 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart',
method: 'POST',
userAgentDirectives: [ [Object] ],
paramsSerializer: [Function],
data: PassThrough {
_readableState: [ReadableState],
readable: false,
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
_writableState: [WritableState],
writable: true,
allowHalfOpen: true,
_transformState: [Object],
_flush: [Function: flush],
[Symbol(kCapture)]: false
},
headers: {
'x-goog-api-client': 'gdcl/4.4.0 gl-node/12.16.3 auth/6.0.6',
'content-type': 'multipart/related; boundary=b6356565-0b2d-49cb-96c2-2bd8d442797a',
'Accept-Encoding': 'gzip',
'User-Agent': 'google-api-nodejs-client/4.4.0 (gzip)',
Authorization: 'Bearer ya29.a0AfH6SMDIwG_SasM8QlX3zqGHf8X9Tj71pfvRN7L0jemdUxb4JGZv4TuHhDRwEJr5sVQZGQ4nnibEKpL4xBjjWEVSuLXSIyD5E_vBcOK18NQzR_SYXylQ36WoIj83ZqPXkUjsPEL3cWQiQ-j5dQ7qNx9965KUSu4xa7s',
Accept: 'application/json'
},
params: [Object: null prototype] { uploadType: 'multipart' },
validateStatus: [Function],
retry: true,
body: PassThrough {
_readableState: [ReadableState],
readable: false,
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
_writableState: [WritableState],
writable: true,
allowHalfOpen: true,
_transformState: [Object],
_flush: [Function: flush],
[Symbol(kCapture)]: false
},
responseType: 'json',
retryConfig: {
currentRetryAttempt: 0,
retry: 3,
httpMethodsToRetry: [Array],
noResponseRetries: 2,
statusCodesToRetry: [Array]
}
},
code: 401,
errors: [
{
domain: 'global',
reason: 'authError',
message: 'Invalid Credentials',
locationType: 'header',
location: 'Authorization'
}
]
}
Followup:
Oddly enough, if I deliberately do NOT include the access token in the oAuth2Client, the refresh appears to happen just fine. A bug? Got the idea from this stack exchange thread but the guy seems just as confused as I am.
oAuth2Client.setCredentials({
// COMMENT OUT access_token: account.googleAccessToken,
refresh_token: account.googleRefreshToken
});
ON TOKENS
New Access Token: y**********************************************
But... sending only the refresh token and refreshing the access token with every single request seems contrary to the intended design? I could probably check the clock before each request and not send the access_token when I expect it is about to expire, but doesn't this defeat the purpose of the "automatic refresh"?
_"This library will automatically use a refresh token to obtain a new access token if it is about to expire."_
_"Once the client has a refresh token, access tokens will be acquired and refreshed automatically in the next call to the API."_
Is there a preferred workaround until this is fixed?
Hey folks, we haven't had a chance to dig in and see what's happening here. I don't think anything interesting has changed in the auth stack recently, so it requires some sleuthing.
Further update: I seem to only run into this issue using the google drive.files.create() function, and the oauth2Client.on('tokens'... is never called when I do. Other calls such as files.get() and files.list() refresh the access token just fine however.
@calculuschild From https://github.com/googleapis/google-auth-library-nodejs#oauth2
This library comes with an OAuth2 client that allows you to retrieve an access token and refreshes the token and retry the request seamlessly if you also provide an expiry_date and the token is expired.
(emphasis mine)
Looking at the code you pasted originally:
oAuth2Client.setCredentials({
access_token: googleAccessToken,
refresh_token: googleRefreshToken
});
You provided access_token but not expiry_date (which is available on the tokens argument pass to the tokens event handler). If you provide expiry_date, does it refresh as expected?
We have the same issue here, we've been struggling for days. oauth2Client.on('tokens'... is never called. We also tried passing expiry_date and didn't work.
Having the same problem. Commenting out the access_token doesn't work as well.
Digging further, it gives an error of "invalid_client" & "Oauth2 client not found" when its apparently attempting to get a new accessToken by calling "https://oauth2.googleapis.com/token" and passing the refreshToken.
Can someone please help or fix this issue?
I'm trying to get the expiry_date and pass it with the tokens as someone suggested. But using PassportJS, I'm not getting that with the tokens. I'm trying to decode the AccessToken to get the expiry_date using some jwt libraries (e.g. jsonwebtokens) but its not working. Can someone point me in the right direction on how to achieve this?
Most helpful comment
Is there a preferred workaround until this is fixed?