繁体   English   中英

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

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

我们正在使用GraphQL将我们的后端与我们的移动应用程序连接起来。 我们正在使用Riverpod来处理全局 state、依赖注入等。

我们正在使用提供程序来处理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",
);

这里也有问题:

  1. 更新令牌后,客户端发生了变化,由此,更新令牌的 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): 

这意味着当更新令牌时,接收存储库的身份验证通知程序取决于提供的客户端被更新/更改(遵循链):

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",
);

在条件( 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. 虽然更新令牌时客户端似乎已更改,但客户端本身并未反映更改(它不包括带有令牌的身份验证 Z099FB995346F395E3Z),因此破坏了 GraphQL 客户端: 在此处输入图像描述

在对我们打开的问题发表评论之后,我们将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;

在这一点上,我们不明白这里有什么问题。

更新

我们在StateNotifier及其提供者中删除了此更改。

通过读取在存储库中获取客户端并不能避免错误: 在此处输入图像描述

使用Riverpod处理授权令牌和GraphQL客户端的正确方法是什么?

谢谢你。

问题是authRepositoryProvider

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

在这个片段中, authRepositoryProvider使用的是没有令牌的GraphQLClient 更新令牌时, graphQLClientProvider state 更新良好,但authRepositoryProvider不是因为您使用ref.read避免重建authRepositoryProvider state。 这意味着authRepositoryProvider知道 graphQLClientProvider 的已处置(和卸载) graphQLClientProvider ,这就是错误消息的原因。

有2个解决方案:

  1. 使用ref.watch
final authRepositoryProvider = Provider<AuthRepository>(
  (ref) => GraphQLAuthRepository(client: ref.watch(graphQLClientProvider)),
  name: 'Auth Repository Provider',
);
  1. 传递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);
}

请注意使用readwatch ,第一个不会对更改做出反应,但可以在任何地方使用,而第二个对提供程序的 state 更新做出反应,但只能在另一个提供程序构造函数或提供访问权限的小部件中使用watchref.watch取决于 Riverpod 的版本。

此外,您的代码中还有另一个问题:

  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();

为了提供解决方案,有必要定义哪些元素将重建AuthNotifer.state而不是重新创建通知程序。

您必须考虑基于ref.read访问另一个通知程序,并在 StateController 构造函数中手动创建订阅。

将来,当 Riverpod 1.0 发布时,使用ref.listen可以轻松解决此问题,但现在您需要重写代码。 您可以在bloc-to-bloc通信指南中获得灵感: https://bloclibrary.dev/#/architecture?id=bloc-to-bloc-communication

我认为错误出在您的 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