requests/requests-oauthlib

Scope changes with Microsoft services & `offline_access`

attie-argentum opened this issue · 0 comments

I'm trying to set up OAuth2 for unattended access to Microsoft IMAP servers - the refresh_token is important here.

When providing a request scope set as follows:

  • offline_access
  • https://outlook.office.com/User.Read
  • https://outlook.office.com/IMAP.AccessAsUser.All

The service responds with the following (i.e: offline_access is removed):

  • https://outlook.office.com/User.Read
  • https://outlook.office.com/IMAP.AccessAsUser.All

This results in a warning being raised.

Traceback
Traceback (most recent call last):
  File "./oauth2-test.py", line 51, in <module>
    token = oauth.fetch_token(token_url, client_secret=client_secret, authorization_response=redirect_response)
  File "/usr/lib/python3.8/site-packages/requests_oauthlib/oauth2_session.py", line 366, in fetch_token
    self._client.parse_request_body_response(r.text, scope=self.scope)
  File "/usr/lib/python3.8/site-packages/oauthlib/oauth2/rfc6749/clients/base.py", line 427, in parse_request_body_response
    self.token = parse_token_response(body, scope=scope)
  File "/usr/lib/python3.8/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 441, in parse_token_response
    validate_token_parameters(params)
  File "/usr/lib/python3.8/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 471, in validate_token_parameters
    raise w
Warning: Scope has changed from "https://outlook.office.com/User.Read https://outlook.office.com/IMAP.AccessAsUser.All offline_access" to "https://outlook.office.com/User.Read https://outlook.office.com/IMAP.AccessAsUser.All".

Apparently the offline_access scope should never be returned by Microsoft services, as it's not actually a useful scope for accessing resources (ref).


My current approach (which isn't ideal), is as follows:

oauth = OAuth2Session(client_id, redirect_uri=redirect_uri, scope=scope)
authorization_url, state = oauth.authorization_url(authorize_url)

# remove the `offline_access` scope directly / by hand
oauth.scope.remove('offline_access')

# ... submit the request to authorization_url, and retrieve the token
redirect_response = ...
token = oauth.fetch_token(token_url, client_secret=client_secret, authorization_response=redirect_response)

I'm aware of OAUTHLIB_RELAX_TOKEN_SCOPE (link), but that seems perhaps a little over-permissive.

Perhaps one of the following would be a good idea?

  • A more generic mechanism to permit accepting scope changes
  • A way to supply the expected response set of scopes
  • A list of "I don't mind if these aren't grated" scopes