meteor/meteor · error · Meteor.Error
${e.reason}
Error message
${e.reason} What it means
Thrown inside getScopes() during the Google OAuth handshake when the HTTP request to Google's tokeninfo endpoint (https://www.googleapis.com/oauth2/v1/tokeninfo) fails to complete. The caught exception e comes from OAuth._fetch or request.json(); its .reason is used as the Meteor.Error reason. This aborts Google sign-in because the scopes list cannot be retrieved.
Source
Thrown at packages/google-oauth/google_server.js:206
callback && callback(undefined, response);
return response;
};
const getScopes = async (accessToken, callback) => {
const content = new URLSearchParams({ access_token: accessToken });
let response;
try {
const request = await OAuth._fetch(
`https://www.googleapis.com/oauth2/v1/tokeninfo?${content.toString()}`,
'GET',
{
headers: { Accept: 'application/json' },
}
);
response = await request.json();
} catch (e) {
callback && callback(e);
throw new Meteor.Error(e.reason);
}
callback && callback(undefined, response.scope.split(' '));
return response.scope.split(' ');
};
Google.retrieveCredential = (credentialToken, credentialSecret) =>
OAuth.retrieveCredential(credentialToken, credentialSecret);
View on GitHub (pinned to 5076d2f818)
Solutions
- Verify the accessToken reaching the server is fresh and not expired before it is sent to tokeninfo.
- Confirm the server has outbound HTTPS access to https://www.googleapis.com.
- Inspect the underlying error (note e.reason may be undefined for raw fetch errors; consider falling back to e.message).
- Re-run the client OAuth flow to obtain new tokens and complete the handshake again.
Example fix
// before
} catch (e) {
callback && callback(e);
throw new Meteor.Error(e.reason);
}
// after
} catch (e) {
const reason = e.reason || e.message || 'Failed to fetch Google token info';
callback && callback(e);
throw new Meteor.Error(reason);
} Defensive patterns
Strategy: try-catch
Validate before calling
// Before triggering the login handler, sanity-check the token and egress
import { fetch } from 'meteor/fetch';
async function tokenLooksValid(accessToken) {
if (!accessToken || typeof accessToken !== 'string') return false;
try {
const res = await fetch('https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=' + accessToken);
return res.ok;
} catch {
return false; // network problem — do not proceed
}
} Try / catch
// In the registerLoginHandler / caller, wrap the Google handshake
try {
result = await getServiceDataFromTokens(tokens);
} catch (err) {
// err.message already prefixed with 'Failed to complete OAuth handshake with Google.'
throw new Meteor.Error('google-handshake-failed', err.message);
} Prevention
- Refresh Google tokens before they expire rather than relying on the server to detect expiry.
- Ensure server egress to https://www.googleapis.com is allowed by firewall/proxy.
- Log err.message (not err.reason) since raw fetch errors often lack a reason field.
When it happens
Trigger: The access token sent to getServiceDataFromTokens -> getScopes is expired, revoked, or malformed so googleapis.com returns an error body or non-200; the outbound network request to googleapis.com fails (DNS, proxy, timeout); the response body is not valid JSON so request.json() throws.
Common situations: Expired or revoked access token reaching the server login handler; corporate proxy/firewall blocking googleapis.com; clock skew between server and Google; a malformed serverAuthCode exchanged for tokens that the tokeninfo endpoint then rejects.
Related errors
- Accounts.ui.config: `requestOfflineToken` only supported for
- Accounts.ui.config: `forceApprovalPrompt` only supported for
- Service not configured
- Failed to complete OAuth handshake with Meetup. ${data.error
- Failed to complete OAuth handshake with Meteor developer acc
AI-assisted analysis of meteor/meteor@5076d2f818 (2026-08-13).
Data as JSON: /api/errors/32f06c6ec11a80fb.
Report an issue: GitHub.