fetchAccountDetails method

Future<GoogleAccountDetails> fetchAccountDetails(
  1. Session session, {
  2. required String idToken,
  3. required String? accessToken,
})

Returns the account details named by the verified idToken.

When an accessToken is supplied, Google's userinfo endpoint is consulted as well, but only to fill in profile fields the ID token does not carry. The identity always comes from the ID token.

Implementation

Future<GoogleAccountDetails> fetchAccountDetails(
  final Session session, {
  required final String idToken,
  required final String? accessToken,
}) async {
  final String clientId = config.clientSecret.clientId;

  Map<String, dynamic> data;
  try {
    data = await IdTokenVerifier.verifyOAuth2Token(
      idToken,
      config: GoogleIdTokenConfig(
        clockSkewTolerance: config.clockSkewTolerance,
      ),
      audience: clientId,
    );
  } catch (e) {
    session.logAndThrow('Failed to verify ID token from Google');
  }

  if (accessToken != null) {
    final response = await http.get(
      Uri.https('www.googleapis.com', '/oauth2/v3/userinfo'),
      headers: {'Authorization': 'Bearer $accessToken'},
    );

    if (response.statusCode != 200) {
      session.logAndThrow('Failed to get user info from Google');
    }

    final userInfo = jsonDecode(response.body);
    if (userInfo is! Map<String, dynamic>) {
      session.logAndThrow('Unexpected user info response from Google');
    }

    // Google's userinfo endpoint honours an access token minted by any OAuth
    // client, so - unlike the ID token, whose `aud` is pinned to this
    // application above - an access token proves nothing about who the caller
    // is. Require it to describe the same account the ID token names, and let
    // the verified claims win wherever the two disagree, so userinfo can only
    // contribute profile fields the ID token did not carry.
    if (userInfo['sub'] != data['sub']) {
      session.logAndThrow(
        'Google user info does not match the verified ID token',
      );
    }

    data = {...userInfo, ...data};
  }

  GoogleAccountDetails details;
  try {
    details = _parseAccountDetails(data);
  } catch (e) {
    session.logAndThrow('Invalid user info from Google: $e');
  }

  try {
    final getExtraInfoCallback = config.getExtraGoogleInfoCallback;
    if (accessToken != null && getExtraInfoCallback != null) {
      await getExtraInfoCallback(
        session,
        accountDetails: details,
        accessToken: accessToken,
        transaction: null,
      );
    }
  } catch (e) {
    session.logAndThrow('Failed to get extra Google account info: $e');
  }

  return details;
}