Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updating Firebase token for client #12

Closed
awaik opened this issue Mar 27, 2020 · 9 comments
Closed

Updating Firebase token for client #12

awaik opened this issue Mar 27, 2020 · 9 comments

Comments

@awaik
Copy link
Contributor

awaik commented Mar 27, 2020

Hello, I've started implementing your package and have difficulties with updating the client token for Firebase.
They issue a new token every 3600 seconds. So, I have to have a reliable way to update it in the app.
Right now you in your example you suggest using such an approach

import 'package:gql_http_link/gql_http_link.dart';
import 'package:ferry/ferry.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';

Future<Client> initClient() async {
  await Hive.initFlutter();

  final box = await Hive.openBox("graphql");

  final store = HiveStore(box);

  final cache = Cache(dataStore: store);

  final link = HttpLink("https://graphql-pokemon.now.sh/graphql");

  final client = Client(
    link: link,
    cache: cache,
  );

  return client;
}

I tried to make reliable updating for connecting with the short living token, but I failed. If you have some best practices for this, can you please share?

Maybe, it could be useful to add update() method to Client

class Client {
  final Link link;
  final Cache cache;
  final ClientOptions options;

Thank you for such a nice plugin!

@klavs
Copy link

klavs commented Mar 27, 2020

Hey @awaik, I'm not too familiar with how Firebase updates the token, but I can assume it's at the network request level. If so, we can narrow your issue down to the link instead of the whole client.

The problem is that we might not have implemented an appropriate link for your use-case. Can you describe how the communication occures at the network level? What's the normal traffic; what happens when the token expires; what Firebase expects you to do next?

@awaik
Copy link
Contributor Author

awaik commented Mar 27, 2020

Hello @klavs, I try to explain, please ask more if I miss something :)

Firstly user logged in and get:

Firebase Authentication sessions are long lived. Every time a user signs in, the user credentials are sent to the Firebase Authentication backend and exchanged for a Firebase ID token (a JWT) and refresh token.

Next, for making requests to functions or database we get

Firebase ID tokens are short lived and last for an hour; the refresh token can be used to retrieve new ID tokens. Refresh tokens expire only when one of the following occurs:

As Google guys offer to work with this tokens here https://firebase.google.com/docs/auth/admin/manage-sessions we should check and revoke token in case it outdated.

For example, in Flutter we can check it like this

      _auth.currentUser().then((val) {
          // val.uid - userToken (long lived)
        val.getIdToken(refresh: true).then((onValue) {
          // onValue.token - idToken (short lived)
        });
      });
    }

So, in our app we have states:

  • userToken - long-lived
  • idToken - short-lived, we need them for connecting to the database
  1. userToken - true
  2. userToken - true, idToken - false
  3. userToken - true, idToken - true

And every 3600 seconds idToken become outdated on the server-side.

@awaik
Copy link
Contributor Author

awaik commented Mar 27, 2020

in addition to the previous post - so, we should use the link with token and have possibility to update it globally anytime in the app

  Map<String, String> idToken = {
    'Authorization': 'Bearer $token}'
  };

  HttpLink link = HttpLink(
    "https://example.com/graphql",
    defaultHeaders: idToken,
  );

@klavs
Copy link

klavs commented Mar 27, 2020

@awaik at this moment there isn't a built-in way to retry requests if the previous request has failed with an auth error.

Here you can take a look at an example how a context can be passed along a request to use custom headers.
https://github.com/gql-dart/gql/blob/c8306c308e4a16d65427eb1517708220eec39b3d/gql_http_link/test/gql_http_link_test.dart#L252-L300

You can take a look at the Transform link which can be used to add a context entry on every request. https://github.com/gql-dart/gql/tree/master/gql_transform_link

@awaik
Copy link
Contributor Author

awaik commented Mar 28, 2020

@klavs Thank you for pointing out. For those who will look for decision, made this code:

  Map<String, String> idToken;

  @override
  void initState() {
    // below we create Map where set short live token.
    idToken = {'Authorization': 'Bearer ' + shortLivedToken};
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Query(
            client: client,
            queryRequest: ProfileBmiQuery().copyWith(
              context: Context.fromList(
                [
                  HttpLinkHeaders(
                    headers: idToken,
                  ),
                ],
              ),
            ),
            builder: (
              BuildContext context,
              QueryResponse<$ProfileBmiQuery> response,
              Object clientError,
            ) {
              if (response.loading)
                return Center(child: CircularProgressIndicator());

              profile = response.data.profile ?? {};

              return SomeWidget(profile: profile);

@awaik awaik closed this as completed Mar 28, 2020
@arpitjacob
Copy link

arpitjacob commented Sep 29, 2020

How would I add a header if I am manually getting the stream or result without using a the query widget

@arpitjacob
Copy link

Options seem missing in the client

@codiyer
Copy link

codiyer commented Nov 18, 2020

I think this can be easily taken care y the httpClient option that gql_http_link package provides. FirebaseAuth does provide a stream where you can listen for the changes in id token. You can use this stream subscription and make the appropriate changes before the request is even fired. Here is an example:

class _HttpClient extends http.BaseClient {
  _HttpClient._() {
    startIdTokenListener();
  }

  static final _instance = _HttpClient._();

  final http.Client _client = http.Client();
  String _token;
  StreamSubscription idTokenSubscription;

  static _HttpClient get instance => _instance;

  Future<String> _getTokenFuture;

  Future<http.StreamedResponse> send(http.BaseRequest request) async {
    if (_getTokenFuture != null) {
      await _getTokenFuture;
    }
    // log.d("Bearer $_token");
    request.headers['Authorization'] = "Bearer $_token";
    return _client.send(request);
  }

  void startIdTokenListener() {
    idTokenSubscription = FirebaseAuth.instance.idTokenChanges().listen(idTokenChangeListener);
  }

  @override
  void close() {
    idTokenSubscription?.cancel();
    _client.close();
    super.close();
  }

  void idTokenChangeListener(User firebaseUser) async {
    if (firebaseUser == null) {
      if (_token != null) {
        _token = null;
      }
      return;
    }
    _getTokenFuture = firebaseUser.getIdToken();
    _token = await _getTokenFuture;
    _getTokenFuture = null;
  }
}

and you can use this http client in the HttpLink constructor like this

final link = HttpLink(
      "your-graphql-link",
      httpClient: _HttpClient.instance,
    );

Maybe @klavs or @smkhalsa could let us know whether using the httpClient option for intercepting the requests can cause any side effects.

@moseskarunia
Copy link

moseskarunia commented Jul 18, 2021

For future gitters who stumbled upon this issue, you can refer to this code https://github.com/zino-app/graphql-flutter/blob/beta/packages/graphql/lib/src/links/auth_link.dart , and use it like:

final link = AuthLink(getToken: () {})..concat(HttpLink('<your-url>'));
final client = Client(link: link);

Adding the package to pubspec is not possible since at the time I write this there's a dependency mismatch between graphql and ferry regarding their dependency on rxdart.

Because graphql >=4.0.3-alpha.1 depends on rxdart ^0.26.0 and ferry 0.10.4 depends on rxdart ^0.27.1, graphql >=4.0.3-alpha.1 is incompatible with ferry 0.10.4. So, because horeekaa_core depends on both ferry 0.10.4 and graphql 5.0.0, version solving failed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants