简体   繁体   English

如何使用 Riverpod 处理身份验证令牌和 GraphQL 客户端?

[英]How to handle the authentication token and and GraphQL client with Riverpod?

We are using GraphQL to connect our backend with our mobil application.我们正在使用GraphQL将我们的后端与我们的移动应用程序连接起来。 We are using Riverpod to, among other things, handle global state, dependency injection, etc.我们正在使用Riverpod来处理全局 state、依赖注入等。

We are using a provider to handle the GraphQL cliente without any authentication header at first while the user is not authentication (this will handle unauthenticated request like login or registering), after the user is authenticated, a token provider who provides the token is updated and it must update the client provider who is user for every repository on the application:我们正在使用提供程序来处理GraphQL 客户端,无需任何身份验证 header 首先,而用户未进行身份验证(这将处理未经身份验证的请求,如登录或注册),在用户通过身份验证后,提供令牌的令牌提供程序被更新并它必须更新作为应用程序每个存储库用户的客户端提供程序:

final StateProvider<String> tokenProvider =
    StateProvider<String>((_) => '', name: "Token Provider");
final graphQLClientProvider = Provider<GraphQLClient>(
  (ref) {
    final String token = ref.watch(tokenProvider).state;

    final Link _link = HttpLink(
        'http://192.168.1.36:1337/graphql',
        defaultHeaders: {
          if (token != '') 'Authorization': 'Bearer $token',
        });

    return GraphQLClient(
      cache: GraphQLCache(),
      link: _link,
    );
  },
  name: "GraphQL Client Provider",
);

There is too problems here:这里也有问题:

  1. After updating the token, the client is changed, and from this, the function who updates the token does not finish in a proper way:更新令牌后,客户端发生了变化,由此,更新令牌的 function 并没有以正确的方式完成:
[ +141 ms] E/flutter ( 8361): [ERROR:flutter/lib/ui/ui_dart_state.cc(199)] Unhandled Exception: Bad state: Tried to use AuthNotifier after `dispose` was called.
[        ] E/flutter ( 8361): 
[        ] E/flutter ( 8361): Consider checking `mounted`.
[        ] E/flutter ( 8361): 
[        ] E/flutter ( 8361): #0      StateNotifier._debugIsMounted.<anonymous closure> (package:state_notifier/state_notifier.dart:128:9)
[        ] E/flutter ( 8361): #1      StateNotifier._debugIsMounted (package:state_notifier/state_notifier.dart:135:6)
[        ] E/flutter ( 8361): #2      StateNotifier.state= (package:state_notifier/state_notifier.dart:155:12)
[        ] E/flutter ( 8361): #3      AuthNotifier.signIn (package:thesis_cancer/features/auth/application/auth.notifier.dart:84:7)
[        ] E/flutter ( 8361): <asynchronous suspension>
[        ] E/flutter ( 8361): #4      _LoginCardState._submit (package:flutter_login/src/widgets/auth_card.dart:503:15)
[        ] E/flutter ( 8361): <asynchronous suspension>
[        ] E/flutter ( 8361): 

It means when the token is updated, the authentication notifier who receives the repository with depends on the provided client is updated/changes (following the chain):这意味着当更新令牌时,接收存储库的身份验证通知程序取决于提供的客户端被更新/更改(遵循链):

final authRepositoryProvider = Provider<AuthRepository>(
  (ref) => GraphQLAuthRepository(client: ref.read(graphQLClientProvider)),
  name: 'Auth Repository Provider',
);

final authNotifierProvider = StateNotifierProvider<AuthNotifier, AuthState>(
  (ref) => AuthNotifier(
    authRepository: ref.watch(authRepositoryProvider),
    dataStore: ref.watch(dataStoreRepositoryProvider),
    profileRepository: ref.watch(profileRepositoryProvider),
    tokenController: ref.watch(tokenProvider.notifier),
    userController: ref.watch(userEntityProvider.notifier),
  ),
  name: "Authentication Notifier Provider",
);

The notification function who breaks at the conditional( if ):在条件( if )处中断的通知 function:

Future<String?> signIn({
    required String username,
    required String password,
  }) async {
    try {
      final Map<String, dynamic> rawUser = await authRepository.signIn(
          identifier: username, password: password) as Map<String, dynamic>;
      final User sessionUser = User.fromJson(rawUser);
      if (sessionUser.confirmed != false) {
        tokenController.state = sessionUser.token!; <-- Bug begins here
      }
      await dataStore.writeUserProfile(sessionUser);
      userController.state = sessionUser;
      state = const AuthState.loggedIn();
    } on LogInFailureByBadRequest {
      return "E-posta veya şifre geçersiz.";
    } on LogInFailure catch (error) {
      return error.toString();
    }
  }
  1. While the client appears to be changed when the token is updated, the client itself does not reflect the change (it does not include the authentication header with the token) and thus breaks the GraphQL client:虽然更新令牌时客户端似乎已更改,但客户端本身并未反映更改(它不包括带有令牌的身份验证 Z099FB995346F395E3Z),因此破坏了 GraphQL 客户端: 在此处输入图像描述

Following the comments on the issue we opened , we added the ProviderReference to our notifier (following this question ) but it does not work:在对我们打开的问题发表评论之后,我们将ProviderReference添加到我们的通知程序(在此问题之后),但它不起作用:

 AuthNotifier(
    this._ref, {
    required this.authRepository,
    required this.profileRepository,
    required this.dataStore,
    required this.tokenController,
    required this.userController,
  }) : super(const AuthState.loading());
  final ProviderReference _ref;
  final ProfileRepository profileRepository;
  final AuthRepository authRepository;
  final DataStoreRepository dataStore;
  final StateController<User?> userController;
  final StateController<String> tokenController;

At this point, we don't understand what's the problem here.在这一点上,我们不明白这里有什么问题。

UPDATE更新

We removed this changes both in the StateNotifier and its provider.我们在StateNotifier及其提供者中删除了此更改。

Getting the client at repository by read does not avoid the error:通过读取在存储库中获取客户端并不能避免错误: 在此处输入图像描述

What's the proper way to handle the authorization token and the GraphQL client with Riverpod ?使用Riverpod处理授权令牌和GraphQL客户端的正确方法是什么?

Thank you.谢谢你。

The problem is the authRepositoryProvider :问题是authRepositoryProvider

final authRepositoryProvider = Provider<AuthRepository>(
  (ref) => GraphQLAuthRepository(client: ref.read(graphQLClientProvider)),
  name: 'Auth Repository Provider',
);

In this fragment, the authRepositoryProvider is using a GraphQLClient without token.在这个片段中, authRepositoryProvider使用的是没有令牌的GraphQLClient When the token is updated, the graphQLClientProvider state is well updated, but the authRepositoryProvider isn't because you are using ref.read avoiding the rebuild of the authRepositoryProvider state.更新令牌时, graphQLClientProvider state 更新良好,但authRepositoryProvider不是因为您使用ref.read避免重建authRepositoryProvider state。 This implies that the authRepositoryProvider knows a disposed (and unmounted) state of graphQLClientProvider and that's why the error message.这意味着authRepositoryProvider知道 graphQLClientProvider 的已处置(和卸载) graphQLClientProvider ,这就是错误消息的原因。

There are 2 solutions:有2个解决方案:

  1. Using ref.watch :使用ref.watch
final authRepositoryProvider = Provider<AuthRepository>(
  (ref) => GraphQLAuthRepository(client: ref.watch(graphQLClientProvider)),
  name: 'Auth Repository Provider',
);
  1. Passing the ref.read as parameter:传递ref.read作为参数:
final authRepositoryProvider = Provider<AuthRepository>(
  (ref) => GraphQLAuthRepository(client: ref.read),
  name: 'Auth Repository Provider',
);

class AuthRepository {
  AuthRepository(this.read)

  final Reader read;

  GraphQLClient get client => read(graphQLClientProvider);
}

Be care about using read and watch , the first one does not react to the changes but could be used anywhere, while the second one reacts to a provider's state update but only can be used in another provider constructor or in a widget that provides access to a watch or ref.watch depending on the version of Riverpod.请注意使用readwatch ,第一个不会对更改做出反应,但可以在任何地方使用,而第二个对提供程序的 state 更新做出反应,但只能在另一个提供程序构造函数或提供访问权限的小部件中使用watchref.watch取决于 Riverpod 的版本。

In addition, there is another problem in your code:此外,您的代码中还有另一个问题:

  final User sessionUser = User.fromJson(rawUser);
  if (sessionUser.confirmed != false) {
    // In this moment, the token is updated,
    // then the QrahpQLClient is updated,
    // then the AuthNotifier is recreated,
    // i.e the current is disposed
    // and now becomes the previous
    tokenController.state = sessionUser.token!; <-- Bug begins here
  }
  await dataStore.writeUserProfile(sessionUser);
  userController.state = sessionUser;

  // In this moment you are updating the state of a disposed notifier
  state = const AuthState.loggedIn();

To provide a solution it is necessary to define which elements will make rebuild the AuthNotifer.state instead of recreating the notifier.为了提供解决方案,有必要定义哪些元素将重建AuthNotifer.state而不是重新创建通知程序。

You must consider access to another notifiers bases on the ref.read and manually create the subscriptions in the StateController constructor.您必须考虑基于ref.read访问另一个通知程序,并在 StateController 构造函数中手动创建订阅。

In the future, when riverpod 1.0 will be released, this issue will be easy with ref.listen but now you need to rewrite your code.将来,当 Riverpod 1.0 发布时,使用ref.listen可以轻松解决此问题,但现在您需要重写代码。 You can take inspiration in the bloc-to-bloc communication gide: https://bloclibrary.dev/#/architecture?id=bloc-to-bloc-communication您可以在bloc-to-bloc通信指南中获得灵感: https://bloclibrary.dev/#/architecture?id=bloc-to-bloc-communication

i think the error is in your AuthNotifier:我认为错误出在您的 AuthNotifier 中:

  if (sessionUser.confirmed != false) {
    tokenController.state = sessionUser.token!; <-- you are updating a dependencie of your actual AuthNotifier object
  }
  await dataStore.writeUserProfile(sessionUser); <-- during the await a new object is created and the actual instance is disposed so this line will throw

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM