簡體   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