gildas-lormeau/SingleFile · error · Error
unknown_token
unknown_token
Error message
unknown_token
What it means
gdrive's refreshAuthToken exchanges the stored refresh token with Google's OAuth token endpoint. A 400 response means Google rejected the refresh grant — typically an invalid, expired, or revoked refresh token — so the library throws 'unknown_token'.
Source
Thrown at src/lib/gdrive/gdrive.js:91
} else {
delete this.accessToken;
delete this.refreshToken;
delete this.expirationDate;
}
}
}
async refreshAuthToken() {
if (this.refreshToken) {
const httpResponse = await fetch(TOKEN_URL, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: "client_id=" + this.clientId +
"&refresh_token=" + this.refreshToken +
"&grant_type=refresh_token" +
"&client_secret=" + this.clientKey
});
if (httpResponse.status == 400) {
throw new Error("unknown_token");
}
const response = await getJSON(httpResponse);
this.accessToken = response.access_token;
if (response.refresh_token) {
this.refreshToken = response.refresh_token;
}
if (response.expires_in) {
this.expirationDate = Date.now() + (response.expires_in * 1000);
}
return { accessToken: this.accessToken, refreshToken: this.refreshToken, expirationDate: this.expirationDate };
} else {
try {
if (browser.identity && browser.identity.removeCachedAuthToken && this.accessToken) {
await browser.identity.removeCachedAuthToken({ token: this.accessToken });
}
this.accessToken = await getAuthToken({ interactive: false });
return { revokableAccessToken: this.accessToken };
// eslint-disable-next-line no-unused-varsView on GitHub (pinned to 517fb7c5cf)
Solutions
- Re-run the interactive OAuth flow to obtain a fresh refresh token (delete stored credentials first).
- Verify clientId/clientKey (secret) match the OAuth client that issued the refresh token.
- Ask the user to re-authorize the app in Google Account permissions if it was revoked.
- Catch this error in upload code and fall back to launching a new auth flow instead of retrying the same token.
Example fix
// before
await gdrive.auth({ refreshToken: storedToken }); // throws unknown_token
// after
try {
await gdrive.auth({ refreshToken: storedToken });
} catch (e) {
if (e.message === 'unknown_token') {
await gdrive.auth({ interactive: true }); // new OAuth flow
} else throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
// verify a refresh token exists and matches current client credentials before auth
if (!refreshToken || clientId !== CLIENT_ID_USED_AT_ISSUANCE || clientKey !== CLIENT_SECRET) {
await gdrive.auth({ interactive: true });
} Try / catch
try {
await gdrive.auth({ refreshToken });
} catch (e) {
if (e.message === 'unknown_token') {
await gdrive.auth({ interactive: true }); // re-run OAuth to get a new refresh token
} else throw e;
} Prevention
- Never retry indefinitely with the same rejected refresh token
- Detect client_id/client_secret changes after switching OAuth clients
- Inform users when re-authorization is needed (password change, revoked access)
When it happens
Trigger: Calling auth/upload with a refresh token that has been revoked, expired (unused for 6+ months), invalidated by changing the Google account password, or issued for a different client_id/client_secret pair.
Common situations: Users revoking app access in Google Account settings; Google auto-expiring dormant refresh tokens; dev/client_secret mismatch after switching OAuth clients; multiple authorizations invalidating older tokens on unverified apps.
Related errors
- error.message + " (Google Drive)"
- error.message + " (Dropbox)"
- invalid_auth_response
- auth_not_supported
- code_required
AI-assisted analysis of gildas-lormeau/SingleFile@517fb7c5cf (2026-09-01).
Data as JSON: /api/errors/54e5e79e4e9f4dc7.
Report an issue: GitHub.