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

  1. Verify the accessToken reaching the server is fresh and not expired before it is sent to tokeninfo.
  2. Confirm the server has outbound HTTPS access to https://www.googleapis.com.
  3. Inspect the underlying error (note e.reason may be undefined for raw fetch errors; consider falling back to e.message).
  4. 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

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


AI-assisted analysis of meteor/meteor@5076d2f818 (2026-08-13). Data as JSON: /api/errors/32f06c6ec11a80fb. Report an issue: GitHub.