2

google_sign_in does not return refreshToken. Is there a way to sign in with Google and receive a refresh token which can be sent to the API for further access to the user's data?

Refresh token could be also obtained with serverAuthCode which is always null at the moment. There are multiple issues created already describing this issue:

Is there any way to authenticate with Google Sign In and receive either refreshToken or serverAuthCode?

RMK
  • 1,897
  • 1
  • 10
  • 17

1 Answers1

4

Google Sign In is based on oAuth2, one can create own implementation of the process.

Here's a code snippet of a google sign in service which can be used to retrieve refreshToken:

import 'dart:async';
import 'dart:html' as html;

import 'package:oauth2/oauth2.dart' as oauth2;

class GoogleSignInService {
  final authorizationEndpoint =
      Uri.parse('https://accounts.google.com/o/oauth2/v2/auth');
  final tokenEndpoint = Uri.parse('https://oauth2.googleapis.com/token');

  final String identifier;
  final String secret;
  final String baseUrl;
  final List<String> scopes;

  _SignInSession? _signInSession;

  Uri get redirectUrl => Uri.parse('$baseUrl/callback.html');

  GoogleSignInService({
    required this.identifier,
    required this.secret,
    required this.baseUrl,
    required this.scopes,
  }) {
    html.window.addEventListener('message', _eventListener);
  }

  void _eventListener(html.Event event) {
    _signInSession?.completeWithCode((event as html.MessageEvent).data);
  }

  Future<GoogleSignInUser?> signIn() async {
    if (_signInSession != null) {
      return null;
    }

    final grant = oauth2.AuthorizationCodeGrant(
      identifier,
      authorizationEndpoint,
      tokenEndpoint,
      secret: secret,
    );

    var authorizationUrl = grant.getAuthorizationUrl(
      redirectUrl,
      scopes: scopes,
    );

    final url =
        '${authorizationUrl.toString()}&access_type=offline&prompt=select_account+consent';
    _signInSession = _SignInSession(url);
    final code = await _signInSession!.codeCompleter.future;

    if (code != null) {
      final client = await grant.handleAuthorizationResponse({'code': code});
      return GoogleSignInUser(
        accessToken: client.credentials.accessToken,
        refreshToken: client.credentials.refreshToken,
        idToken: client.credentials.idToken!,
      );
    } else {
      return null;
    }
  }
}

class GoogleSignInUser {
  final String accessToken;
  final String? refreshToken;
  final String idToken;

  const GoogleSignInUser({
    required this.accessToken,
    required this.refreshToken,
    required this.idToken,
  });

  @override
  String toString() {
    return 'GoogleSignInUser{accessToken: $accessToken, refreshToken: $refreshToken, idToken: $idToken}';
  }
}

class _SignInSession {
  final codeCompleter = Completer<String?>();
  late final html.WindowBase _window;
  late final Timer _timer;

  bool get isClosed => codeCompleter.isCompleted;

  _SignInSession(String url) {
    _window =
        html.window.open(url, '_blank', 'location=yes,width=550,height=600');
    _timer = Timer.periodic(const Duration(milliseconds: 500), (timer) {
      if (_window.closed == true) {
        if (!isClosed) {
          codeCompleter.complete(null);
        }
        _timer.cancel();
      }
    });
  }

  void completeWithCode(String code) {
    if (!isClosed) {
      codeCompleter.complete(code);
    }
  }
}

Make sure to also create web/callback.html file:

<html>
<body>
</body>
<script>
        function findGetParameter(parameterName) {
            var result = null,
            tmp = [];
            location.search
                .substr(1)
                .split("&")
                .forEach(function (item) {
                    tmp = item.split("=");
                    if (tmp[0] === parameterName) result = decodeURIComponent(tmp[1]);
                 });
            return result;
         }
        let code = findGetParameter('code');

        window.opener.postMessage(code, "http://localhost:5000");
        window.close();
    </script>
</html>

Change http://localhost:5000 to whatever domain you're using in production.

Exemplary usage:

final googleSignIn = GoogleSignInService(
        identifier: 'CLIENT_ID',
        secret: 'CLIENT_SECRET',
        baseUrl: 'http://localhost:5000',
        scopes: [
          'email',
        ],
      ),
final user = await googleSignIn.signIn();
RMK
  • 1,897
  • 1
  • 10
  • 17