in app/lib/account/google_oauth2.dart [107:184]
Future<AuthResult?> _tryAuthenticateJwt(String jwt) async {
// Hit the token-info end-point documented at:
// https://developers.google.com/identity/sign-in/web/backend-auth
// Note: ideally, we would verify these JWTs locally, but unfortunately
// we don't have a solid RSA implementation available in Dart.
final u = _tokenInfoEndPoint.replace(queryParameters: {'id_token': jwt});
final response = await retry(
() => _httpClient.get(u, headers: {'accept': 'application/json'}),
maxAttempts: 2, // two attempts is enough, we don't want delays here
);
// Expect a 200 response
if (response.statusCode != 200) {
return null;
}
final r = json.decode(response.body) as Map<String, dynamic>;
if (r.containsKey('error')) {
return null; // presumably an invalid token.
}
// Sanity check on the algorithm
if (r['alg'] == 'none') {
_logger.warning('JWT rejected, alg = "none"');
return null;
}
// Sanity check on the algorithm
final typ = r['typ'];
if (typ != 'JWT') {
_logger.warning('JWT rejected, typ = "$typ');
return null;
}
// Validate the issuer.
final iss = r['iss'];
if (iss != 'accounts.google.com' && iss != 'https://accounts.google.com') {
_logger.warning('JWT rejected, iss = "$iss');
return null;
}
// Validate create time
final fiveMinFromNow = clock.now().toUtc().add(Duration(minutes: 5));
final iat = r['iat'];
if (iat == null || _parseTimestamp(iat).isAfter(fiveMinFromNow)) {
_logger.warning('JWT rejected, iat = "$iat"');
return null; // Token is created more than 5 minutes in the future
}
// Validate expiration time
final fiveMinInPast = clock.now().toUtc().subtract(Duration(minutes: 5));
final exp = r['exp'];
if (exp == null || _parseTimestamp(exp).isBefore(fiveMinInPast)) {
_logger.warning('JWT rejected, exp = "$exp"');
return null; // Token is expired more than 5 minutes in the past
}
// Validate audience
final aud = r['aud'];
if (aud is! String) {
_logger.warning('JWT rejected, aud missing');
return null; // missing audience
}
if (!_trustedAudiences.contains(aud)) {
_logger.warning('JWT rejected, aud = "$aud"');
return null; // Not trusted audience
}
// Validate subject is present
final sub = r['sub'];
if (sub is! String) {
_logger.warning('JWT rejected, sub missing');
return null; // missing subject (probably missing 'openid' scope)
}
// Validate email is present
final email = r['email'];
if (email is! String) {
_logger.warning('JWT rejected, email missing');
return null; // missing email (probably missing 'email' scope)
}
final emailVerified = r['email_verified'];
if (emailVerified != true && emailVerified != 'true') {
_logger.warning('JWT rejected, email_verified = "$emailVerified"');
return null; // missing email (probably missing 'email' scope)
}
return AuthResult(sub, email);
}