Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix DEV stack authentication url with localhost redirect_uri #743

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

willemarcel
Copy link
Collaborator

No description provided.

@willemarcel willemarcel requested a review from jake-low August 26, 2024 16:18
@jake-low
Copy link
Contributor

I tried testing this. What I observed was:

  1. I clicked "Sign in" in OSMCha
  2. I was directed to osm.org to authorize OSMCha to access my account
  3. After clicking "Authorize" I was directed back to OSMCha, but instead of being logged in, I see an error page that says Source authorized does not exist

Is that expected? In Slack you mentioned that there's a backend issue that affects this too, so maybe that's what I'm seeing, but just wanted to check.

@willemarcel
Copy link
Collaborator Author

@jake-low I think this solution will not work. I'm seeing this error in the osmcha backend when I try to login:
oauthlib.oauth2.rfc6749.errors.InvalidGrantError: (invalid_grant) The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.

@batpad
Copy link

batpad commented Aug 27, 2024

Hmm .. can we try:

  • Go the OAuth2 Application Settings in your OSM Profile (My Settings) for the OAuth2 app keys being used
  • Check the Redirect URIs
  • I think you will need to add something like http://127.0.0.1:8000 to the list of allowed redirect URIs

Another thing to try is maybe redirecting to http://localhost:8000 instead of http://127.0.0.1:8000 here: https://github.com/OSMCha/osmcha-frontend/pull/743/files - this redirect is basically being performed by openstreetmap.org and it won't allow redirecting to arbitrary URIs not in the list of allowed Redirect URIs in your application config. There might be an exception for localhost that does not get applied when it's written as 127.0.0.1

Am pretty certain this should be the issue - if 127.0.0.1 already exists in the list of allowed Redirect URIs, then I don't know what's going on ...

@willemarcel
Copy link
Collaborator Author

@batpad I have 127.0.0.1/authorized in the allowed redirect urls of the oauth2 key, OSM oauth2 doesn't allow localhost.

A solution I used on another project was to add another endpoint in the backend to authenticate and redirect the user to 127.0.0.1

@jake-low
Copy link
Contributor

I was looking into this a bit yesterday. I think I understand the problem with this approach now, though I still need to think a bit more about how to solve it.

The InvalidGrantError is happening at the end of the authentication flow. The flow looks roughly like this:

  • the user clicks "sign in" on osmcha.org. this triggers an empty POST request to osmcha.org/api/v1/social-auth/. The backend replies with a an OAuth login URL to openstreetmap.org, and the frontend opens that page for the user. What this PR changes is that the frontend modifies the URL before opening it, changing the redirect_uri parameter set by the server.
  • the user clicks "authorize" on openstreetmap.org, which redirects them back to where they came from (more specifically, back to the specified redirect_uri, assuming it is on the approved list of redirect URIs registered for the application). An authorization code is included as a query parameter on this URL.
  • the frontend pulls the query parameter out of the URL and POSTs it to the backend (on the same osmcha.org/api/v1/social-auth/ endpoint as before, but with a body this time).
  • the backend attempts to use that code to fetch an access token from openstreetmap.org. this access token is what will allow the backend to interact with the openstreetmap.org API on the user's behalf. This is where the error occurs.

When requesting an access token, the backend must provide the authorization code to the OAuth provider (openstreetmap.org). But it must also provide the redirect URI, and it is an error if this redirect URI doesn't match what the frontend used when it generated the code (see RFC 6749 Section 4.1.3 for the specifics).

The frontend has tampered with the URL that the backend provided it, but the backend doesn't know this. By itself, this tampering is fine (anyone can generate these URLs; you don't need to know the application secret). The authorization flow succeeds, at least from the perspective of openstreetmap.org, and a valid authorization code is sent back to the frontend via redirect. But the backend doesn't know the correct redirect URI required to use this code. When it tries to use the code with the incorrect redirect URI to fetch an access token, openstreetmap.org returns an error.


The solution to this is to have the backend somehow know the correct redirect URI to use for a code, which on its own isn't too hard. For example, instead of using a fixed value from an environment variable, it could use the value of the Origin header of the HTTP request to the /api/v1/social-auth/ endpoint. That would allow different frontends hosted on different domains to use the same backend. (This is roughly what MapRoulette does to support running the frontend on localhost but having it talk to the staging or prod backend).

However, the requests-oauthlib package doesn't seem to be designed for this use case. It's OAuth2Session class takes the redirect_uri as an __init__ parameter, and doesn't allow modifying it later. You might be able to create a separate session object for each request, but I'm not sure if this is possible (there may be some state stored in the session that needs to match between the initial auth request and the callback request) or cryptographically safe.

Another possibility would be to forego the requests-oauthlib package and just use oauthlib directly.


For convenience, here is the full stack track of the error from the backend. I have no idea why the message at the end is in Czech. It translates to "The authorization grant provided is invalid, expired, revoked, does not match the redirect URI used in the authorization request, or was issued to another client".

Internal Server Error: /api/v1/social-auth/
Traceback (most recent call last):
  File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 34, in inner
    response = get_response(request)
  File "/usr/local/lib/python3.10/site-packages/django/core/handlers/base.py", line 115, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/usr/local/lib/python3.10/site-packages/django/core/handlers/base.py", line 113, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/usr/local/lib/python3.10/contextlib.py", line 79, in inner
    return func(*args, **kwds)
  File "/usr/local/lib/python3.10/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/usr/local/lib/python3.10/site-packages/django/views/generic/base.py", line 71, in view
    return self.dispatch(request, *args, **kwargs)
  File "/usr/local/lib/python3.10/site-packages/rest_framework/views.py", line 505, in dispatch
    response = self.handle_exception(exc)
  File "/usr/local/lib/python3.10/site-packages/rest_framework/views.py", line 465, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/usr/local/lib/python3.10/site-packages/rest_framework/views.py", line 476, in raise_uncaught_exception
    raise exc
  File "/usr/local/lib/python3.10/site-packages/rest_framework/views.py", line 502, in dispatch
    response = handler(request, *args, **kwargs)
  File "/app/osmchadjango/users/views.py", line 111, in post
    access_token = self.get_access_token(
  File "/app/osmchadjango/users/views.py", line 87, in get_access_token
    return self.consumer.fetch_token(
  File "/usr/local/lib/python3.10/site-packages/requests_oauthlib/oauth2_session.py", line 406, in fetch_token
    self._client.parse_request_body_response(r.text, scope=self.scope)
  File "/usr/local/lib/python3.10/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/local/lib/python3.10/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 441, in parse_token_response
    validate_token_parameters(params)
  File "/usr/local/lib/python3.10/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 448, in validate_token_parameters
    raise_from_error(params.get('error'), params)
  File "/usr/local/lib/python3.10/site-packages/oauthlib/oauth2/rfc6749/errors.py", line 399, in raise_from_error
    raise cls(**kwargs)
oauthlib.oauth2.rfc6749.errors.InvalidGrantError: (invalid_grant) Poskytnutý grant pro udělení oprávnění je neplatný, jeho platnost vypršela, zrušena, neodpovídá identifikátoru URI přesměrování použitého v žádosti o autorizaci nebo byla vydána jinému klientovi.

@willemarcel
Copy link
Collaborator Author

@jake-low feel free to try the solution you proposed, although I think this issue is not a high priority right now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants