The problem of cyclic dependence in constructing the links between AuthService and FreshLink for OAuth2.0 authorization in GraphQL in Flutter

The problem of cyclic dependence in constructing the links between AuthService and FreshLink for OAuth2.0 authorization in GraphQL in Flutter


0

In my project, I use the fresh_graphql package to refresh tokens at the GraphQLClient link level. This package adds the access token to headers, manages token storage, and handles authorization errors, after which it refreshes the tokens.

I plan to create a similar link that will only add tokens to headers and handle authorization errors, so that token management can be done manually in the authorization service.

I have an AuthService that uses AuthRepository to make requests to the server. AuthRepository uses GraphQLClient to make requests to the server. GraphQLClient uses FreshLink to automatically refresh tokens. FreshLink uses AuthService to get the current tokens.

Listing AuthService:

class AuthService {
  final AuthRepository _authRepository;

  final _authenticationStatusController = StreamController<bool>.broadcast();

  const AuthService({
    required AuthRepository authRepository,
  }) : _authRepository = authRepository;

  bool get isAuthenticated => _authRepository.currentAuthTokens != null;

  Stream<bool> get authenticationStatus => _authenticationStatusController.stream;

  Future<void> login(String email, String password) async {
    final authTokens = await _authRepository.login(email, password);
    
    _authenticationStatusController.add(true);

    await _authRepository.saveAuthTokens(authTokens);
  }

  Future<void> logout() async {
    await _authRepository.logout();
    await _authRepository.deleteAuthTokens();
    _authenticationStatusController.add(false);
  }

  Future<void> refreshTokens() async {
    final authTokens = await _authRepository.refreshTokens();

    await _authRepository.saveAuthTokens(authTokens);
  }

  Future<void> revokeTokens() async {
    await _authRepository.deleteAuthTokens();
    _authenticationStatusController.add(false);
  }

  void dispose() {
    _authenticationStatusController.close();
  }
}

Listing AuthRepository:

class AuthRepository {
  final GraphQLClient _graphQLClient;

  const AuthRepository({
    required GraphQLClient graphQLClient,
  }) : _graphQLClient = graphQLClient;

  Future<AuthTokens> login(String email, String password) async {
    const query = r'''
      mutation Login($email: String!, $password: String!) {
        login(email: $email, password: $password) { accessToken refreshToken } 
      }
      ''';

    final options = MutationOptions(
      document: gql(query),
      variables: {
        'email': email,
        'password': password,
      },
    );

    final result = await _graphQLClient.mutate(options);

    if (result.hasException) throw result.exception!;

    return AuthTokensGraphQL.fromJson(result.data!['login']);
  }

  Future<void> logout() async {
    // logout
  }

  AuthTokens? get currentAuthTokens {
    // fetch from local storage
  }

  Future<void> saveAuthTokens(AuthTokens authTokens) async {
    // save to local storage
  }

  Future<void> deleteAuthTokens() async {
    // delete from local storage
  }
}

Listing FreshLink:

class FreshLink extends Link {
  final AuthService _authService;

  FreshLink({
    required AuthService authService,
  }) : _authService = authService;

  @override
  Stream<Response> request(Request request, [NextLink? forward]) async* {
    // add access token to headers
    // do request
    // handle unauthorized error
  }
}

Listing GraphQLClient:

final freshLink = FreshLink(
  authService: authService,
);

final httpLink = HttpLink(
  'https://example.app/graphql',
);

final links = Link.from([freshLink, httpLink]);

final graphQLClient = GraphQLClient(
  cache: GraphQLCache(),
  link: links,
);

As you can see, AuthService uses AuthRepository, which uses GraphQLClient, which uses FreshLink, which uses AuthService.

How can I fix this problem?

New contributor

Yevhenii Aleksiuk is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.


Load 3 more related questions


Show fewer related questions

0



Leave a Reply

Your email address will not be published. Required fields are marked *