I have a scenario where I am obtaining access/refresh tokens via oAuth consent. When the access token expires, the refreshed token returned after refresh is losing scopes previously acquired.
Summary of the interesting parts of the code look like this:
// I'm using google-auth-library
import { OAuth2Client } from "google-auth-library";
...
// I create an OAuth2Client like this. Let's assume the parameters are correct (they are)
new OAuth2Client(
clientId,
clientSecret,
redirectUri
);
When obtaining consent, I generate a URL like this (this.newOAuth2Client
just creates the client as described above):
// Generate a url that asks permissions for the Drive activity scope
const authorizationUrl = this.newOAuth2Client(redirectUri).generateAuthUrl({
// 'online' (default) or 'offline' (gets refresh_token)
access_type: 'offline',
prompt : 'select_account',
// Pass in the scopes array.
scope: scopes,
// Enable incremental authorization. Recommended as a best practice.
include_granted_scopes: true,
// The value to be passed to the redirect URL.
state: state
});
When it comes time to refresh the token, it looks like this:
const client = this.newOAuth2Client(redirectUri);
client.setCredentials({ refresh_token: refresh_token });
return await client.getAccessToken();
The token returned produces a 401 error against the (Drive) API I am accessing, for requests that worked just fine prior to the refresh.
This is how I’m pulling the updated tokens from the refresh:
/**
* The client.getAccessToken() returns something called a GetAccessTokenResponse,
* which seems to be undocumented (?). Hunting and pecking...
*/
const result = client.getAccessToken();
const response = result.res;
if(response.status == 200) {
const credentials = response.data;
const accessToken = credentials.access_token; // <== This seems broken
const refreshToken = credentials.refresh_token;
}
The only other piece of relevant data might be that these lost scopes were originally obtained after initial consent. In my particular case, I am first requesting these scopes:
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile'
Later, I add the following scopes:
'https://www.googleapis.com/auth/drive.appdata',
'https://www.googleapis.com/auth/drive.appfolder',
'https://www.googleapis.com/auth/drive.file'
After the refresh, the scopes seem to revert to only the initial set. After this subsequent grant, there is an update to the access_token
(as expected) and if there is a refresh_token
present I will persist it, but generally a refresh_token
is not provided on this incremental grant.
Is it expected that refreshing an access token would wipe out scopes, or is there something wonky with my approach? (or perhaps my problem is elsewhere entirely?)