teranetsrl / oauth2_client

Simple Dart library for interacting with OAuth2 servers.
BSD 2-Clause "Simplified" License
97 stars 113 forks source link

Can't get stored tokens with Azure Active Directory's “offline_access” scope #138

Open maltekiessling opened 2 years ago

maltekiessling commented 2 years ago

Roughly summarized, how the storing and reading of the storage works:

After retrieving the /token endpoint these received scopes are stored together with the tokens in the storage. When reading the tokens from the storage the getToken method of the TokenStorage class checks if the needed scopes match the stored scopes.

This is done somewhat like this:

var s1 = Set.from(storedScopes);
var s2 = Set.from(requiredScopes);
found = s1.intersection(s2).length == requiredScopes.length;

Now to the implementation of OAuth2 in AAD: AAD returns a refresh_token only if you add the scope "offline_access". However, this scope is not returned by the /token endpoint.

This now results in the problem that the tokens can never be read from the storage, because the required scope "offline_access" is never present in the stored scopes.

As a hack, I modified the getTokenFromStorage method of the OAuth2Helper class as follows:

Future<AccessTokenResponse?> getTokenFromStorage() async {
+  final msScopes = [...scopes ?? <String>[]];
+  msScopes.remove('offline_access');
+
+  return await tokenStorage.getToken(msScopes);
-  return await tokenStorage.getToken(scopes ?? []);
}

Is there a good way that I don't have to copy the whole package? Is there an elegant solution to modify the OAuth2Helper class to work with AAD? Or am I missing something fundamental?

okrad commented 2 years ago

Hi @maltekiessling, that's a good point...

Ideally I think that the client should in some way be able to customize some of the helper inner workings. For example, in your case it could instruct the helper to ignore the offline_access scope while storing or fetching it to/from the storage.

Now, actually there is no way of doing it, but I think it would be a nice addition, since it would allow for more flexibility and customization. Just thinking out loud ATM, but probably we could introduce a sort of IoC making the helper invoke callbacks on the client before/after strategic points, for example before the token is written or read from the storage.

For example you could implement a client like this:

import 'package:oauth2_client/oauth2_client.dart';

class AADOAuth2Client extends OAuth2Client {
  AADOAuth2Client(
      {required String redirectUri, required String customUriScheme})
      : super(
            authorizeUrl: '...',
            tokenUrl: '...',
            revokeUrl: '...',
            redirectUri: redirectUri,
            customUriScheme: customUriScheme);

   List<String> beforeFetchTokenFromStorage(List<String> scopes) {
      final msScopes = [...scopes ?? <String>[]];
      msScopes.remove('offline_access');
      return msScopes;
   }
}

The helper could then be modified to invoke the beforeFetchTokenFromStorage callback and using the returned scopes to check the presence of the token in the storage.

The tricky part would be to identify which "strategic points" to introduce to avoid a proliferation of callbacks...

What do you think?

AndrewStickler commented 1 year ago

This is blocking me too. If the offline_access scope is optional in the response from the token api, can't it be excluded from the comparison? I think I will have to drop use of the helper and manage storage myself.

mikeryder commented 6 months ago

Is there any update on this? A new member variable in the OAuth2Client class for ignoredScopesFromMatching would allow for the MicrosoftOAuth2Client class to ignore "offline_access" by default. The list of strings in ignoredScopesFromMatching would be ignored when matching the scopes during the call to getTokenFromStorage. Would you be open to a PR with this change?