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

URLs are not relative to base_url #336

Open
davidism opened this issue Jan 21, 2021 · 2 comments
Open

URLs are not relative to base_url #336

davidism opened this issue Jan 21, 2021 · 2 comments

Comments

@davidism
Copy link

davidism commented Jan 21, 2021

The docs say:

            base_url: The base URL of the OAuth provider.
                If specified, all URLs passed to this instance will be
                resolved relative to this URL.

            authorization_url: The URL specified by the OAuth provider for
                obtaining an
                `authorization grant <http://tools.ietf.org/html/rfc6749#section-1.3>`__.
                This can be an fully-qualified URL, or a path that is
                resolved relative to the ``base_url``.

With base_url="http://example.com/oauth2" and authorization_url="authorize", I get redirected to http://localhost:5000/auth/custom/authorize. Using "/authorize" with a slash at the beginning redirects to http://localhost:5000/authorize. According to the docs, it should be redirecting to http://example.com/oauth2/authorize.

If I use full URLs in order to be able to get to the login, URLs are still not relative when making API requests. For example, custom.session.get("userprofile") tries to get http://localhost:5000/auth/custom/userprofile. According to the docs, it should be getting http://example.com/oauth2/userprofile.

I'm using OpenAM, running locally right now for testing. It's a bit involved to set up for oauth, but here's the example Flask app at least:

import os

from flask import Flask
from flask import redirect
from flask import render_template_string
from flask import session
from flask import url_for
from flask_dance.consumer import OAuth2ConsumerBlueprint

os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"

app = Flask(__name__)
app.secret_key = "dev"

openam = OAuth2ConsumerBlueprint(
    "openam",
    __name__,
    client_id="dance-example",
    client_secret="password",
    base_url="http://localhost:8080/openam/oauth2",
    # have to use full URL here because base_url is not being used
    token_url="http://localhost:8080/openam/oauth2/access_token",
    authorization_url="http://localhost:8080/openam/oauth2/authorize",
    redirect_to="load_profile",
)
app.register_blueprint(openam, url_prefix="/auth")


@app.route("/load-profile")
def load_profile():
    # have to use full URL here because base_url is not being used
    r = openam.session.get("http://localhost:8080/openam/oauth2/userinfo")
    r.raise_for_status()
    data = r.json()
    session["user"] = {
        "id": data["sub"],
        "name": data["name"],
        "roles": data["roles"],
    }
    return redirect(url_for("index"))


@app.route("/logout")
def logout():
    session.clear()
    return redirect(url_for("index"))


@app.route("/")
def index():
    if openam.session.authorized:
        return render_template_string("""Logged in as {{ session["user"]["name"] }}<br><a href="{{ url_for("logout") }}">Log Out</a>""")

    return render_template_string("""Not logged in<br><a href="{{ url_for("openam.login") }}">Log In</a>""")
@singingwolfboy
Copy link
Owner

Hmm... I think we'll need to update the documentation for this. I had entirely forgotten that the docs state that the authorization_url and token_url can be resolved relative to the base_url, and it doesn't look like we have any automated tests that cover this case. Maybe it worked in the past, but doesn't work anymore... or it's entirely possible that it never actually worked this way.

However, while I'm willing to give up on relative paths for authorization_url and token_url, it's a different story for relative paths passed to openam.session.get(). That sort of thing should work, and I use that pattern in several different sample Flask-Dance projects without any problems. For example, it works for GitHub, and Google, and Slack.

In the code, this resolution is handled in the flask_dance.consumer.requests.OAuth2Session.request() function. If you're making an OAuth2ConsumerBlueprint instance, passing base_url, and not passing session_class (so that it uses the default), then that should work properly. For the HTTP request you're making in your load_profile() function, if you change that line to use a relative URL, can you confirm that it is not being resolved properly using the base_url? Because if that's the case, I'll need to know more about your setup, so I can try to figure out why it's not working.

@Vlarabor
Copy link

Vlarabor commented Aug 15, 2021

Looking at the docs of URLObject just using base_url.relative() in the flask_dance_consumer.requests.OAuth2Session.request() function does not work for all cases. Look at the following example from the documentation:

>>> url = URLObject("https://github.com/zacharyvoase/urlobject?spam=eggs#foo")
>>> print(url.relative('another-project'))
https://github.com/zacharyvoase/another-project

For instance, the base_url of the Strava API is https://www.strava.com/api/v3. The following scenarios result in different results for base_url.relative(url)

>>> base_url = URLObject('https://www.strava.com/api/v3')
>>> base_url.relative('/routes/123')
URLObject('https://www.strava.com/routes/123')
>>> base_url.relative('routes/123')
URLObject('https://www.strava.com/api/routes/123')
>>> base_url = URLObject('https://www.strava.com/api/v3/')
>>> base_url.relative('/routes/123')
URLObject('https://www.strava.com/routes/123')
>>> base_url.relative('routes/123')
URLObject('https://www.strava.com/api/v3/routes/123')

In this case, only the last output is correct. However, I'm not sure if there is a simple way to handle all possible cases for all OAuth providers.

Edit: I guess, I should also include the example from the original post:

>>> base_url = URLObject("http://localhost:8080/openam/oauth2")
>>> base_url.relative('/userinfo')
URLObject('http://localhost:8080/userinfo')
>>> base_url.relative('userinfo')
URLObject('http://localhost:8080/openam/userinfo')

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

No branches or pull requests

3 participants