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

authorized_url is http, not https: #188

Closed
aardvark82 opened this issue Nov 24, 2018 · 16 comments
Closed

authorized_url is http, not https: #188

aardvark82 opened this issue Nov 24, 2018 · 16 comments

Comments

@aardvark82
Copy link

I'm calling flask-dance with make_slack_blueprint, and the URL flask-dance sends to Slack as the authorized_url is

"http://mydomain.com/login/slack/authorized"

instead of the proper

"https://mydomain.com/login/slack/authorized"

This means the call fails on my production server since I did not set the insecure HTTPS env variable there (and shouldn't)

how do I get flask dance to pass the https URL for the authorized_url? If I try to specify an absolute path as the authorized url then it gets treated as a relative path.

blueprint_slack = make_slack_blueprint(
    client_id="sdfdsg242894452",
    client_secret="53019238021358rrgdf",
    scope=["identify",  "chat:write:bot"],
    **authorized_url='https://www.mydomain.com/login/slack/authorized',**   
    redirect_url='/slack_authorized',

)

If it is meaningful:

I'm running Flask 1.0+

  • with Flask-talisman 0 all URl's redirect to https:// and I have HSTS set
  • with a gunicorn server, with relevant https flags set in my gunicorn config file

secure_proxy_ssl_header = ('HTTP_X_FORWARDED_PROTO', 'https')
forwarded_allow_ips = '*'
secure_scheme_headers = {'X-Forwarded-Proto': 'https'}
x_forwarded_for_header = 'X-FORWARDED-FOR'

PS: And yes, the client_id and secret above are bogus!

@Lexy2
Copy link

Lexy2 commented Nov 24, 2018

Why do you specify the redirect_url explicitly?
Flask-Dance generates /login/slack and /login/slack/authorized views automatically, you don't have to specify them. The redirect_url is something your app will show after the user has clicked 'Add to Slack' button and all the OAuth dance has completed.
OAuth2ConsumerBlueprint does not use http scheme, it just adds the url rule relative to the root of your WSGI app. So if your app runs in WSGI env of http://mydomain.com, the BP will also generate http://mydomain.com/slack/authorized URL for authorized_url.

You should do something like:

blueprint_slack = make_slack_blueprint(
    client_id="sdfdsg242894452",
    client_secret="53019238021358rrgdf",
    scope=["identify",  "chat:write:bot"],
    redirect_to="authed_handler")
app = Flask(__name__)
app.register_blueprint(blueprint_slack, url_prefix="/login")

@app.route("/slack_authorized")
def authed_handler():
    return "Authorized successfully!"

and then when you go to
https://mydomain.com/login/slack
The app will redirect you with 302 Found to api.slack.com, which will redirect you back to https://mydomain.com/login/slack/authorized, which will then show the user https://mydomain.com/slack_authorized

@aardvark82
Copy link
Author

Thanks for your response - I should have mentioned the code above was my attempt at fixing the problem - my original code was using the default route:

blueprint_slack = make_slack_blueprint(
    client_id="blablabla",
    client_secret="blablabla",
    scope=["identify"],
    redirect_url='/slack_authorized',
)

Which leads to the redirect to http://mydomain.com/login/slack/authorized

The website and calls are all hosted on https:// which is why I mentioned the gunicorn config.

How do I check / set the root of my WSGI app?

@aardvark82
Copy link
Author

aardvark82 commented Nov 24, 2018

FYI the code above is called in a setup() method that is called before_first_request - what's the best place to specify? Can't / don't want to put it inline as I like to keep all init code clean.

@app.before_first_request
def setup():
             setupSlack()  # code above

@aardvark82
Copy link
Author

And to be comprehensive, Flask-Dance login & auth work on my dev server with the insecure HTTPS env variable set so my routes & flow seem to be properly setup. The issue is the default handler uses the http:// base instead of https:// on the production server.

@Lexy2
Copy link

Lexy2 commented Nov 25, 2018

Well, then it might be the issue with gunicorn. Also, check your Slack app redirect URLs.

@Lexy2
Copy link

Lexy2 commented Nov 25, 2018

 redirect_url='/slack_authorized',
)

Which leads to the redirect to http://mydomain.com/login/slack/authorized

Hmm, this redirect_url should redirect to http://mydomain.com/slack_authorized, exactly as you specified.

@aardvark82
Copy link
Author

It does redirect to http://mydomain.com/slack_authorized, but not to https://mydomain.com/slack_authorized which I expect.

Is there a way to fully overload the redirect_url with an absolute path and not a relative one?

@Lexy2
Copy link

Lexy2 commented Nov 25, 2018

Try solutions from here:

app.config.update(dict(
  PREFERRED_URL_SCHEME = 'https'
))

or

class ReverseProxied(object):
    def __init__(self, app):
        self.app = app

    def __call__(self, environ, start_response):
        scheme = environ.get('HTTP_X_FORWARDED_PROTO')
        if scheme:
            environ['wsgi.url_scheme'] = scheme
        return self.app(environ, start_response)

app = Flask(__name__)
app.wsgi_app = ReverseProxied(app.wsgi_app)

@Lexy2
Copy link

Lexy2 commented Nov 25, 2018

Is there a way to fully overload the redirect_url with an absolute path and not a relative one?

Yes, if you specify the URL with the protocol, https://mydomain.com, it will redirect to absolute path.
Not sure how it works with talisman though
Edit: This statement turned out to be false, see below.

@aardvark82
Copy link
Author

aardvark82 commented Nov 25, 2018

Lexy,

Solution #2 worked! Thank you for your help.

class ReverseProxied(object):
    def __init__(self, app):
        self.app = app

    def __call__(self, environ, start_response):
        scheme = environ.get('HTTP_X_FORWARDED_PROTO')
        if scheme:
            environ['wsgi.url_scheme'] = scheme
        return self.app(environ, start_response)

app = Flask(__name__)
app.wsgi_app = ReverseProxied(app.wsgi_app)

After this, the auth_url points to the correct https:// url instead of the http:// one.

screenshot 2018-11-25 02 21 24

The items below are for reference for those encountering a similar issue.

I tried (unsuccessfully)

  • Flask SSLify (instead of Flask-Talisman - to eliminate it as a source of error)
    as well as
app.config.update(dict(
  PREFERRED_URL_SCHEME = 'https'
))

PS: concerning your previous comment, in my case, if I specify the absolute URL with the protocol it treats it as a relative path (see my initial post up top).

 blueprint_slack = make_slack_blueprint(){
      authorized_url='https://www.mydomain.com/login/slack/authorized',**   
}

--> uses relative path

I did not find a way to specify an absolute path.

@Lexy2
Copy link

Lexy2 commented Nov 25, 2018

Great! I am happy that I could be of help.

I did not find a way to specify an absolute path.

Just to add. flask-dance/consumer/base.py:

class BaseOAuthConsumerBlueprint(six.with_metaclass(ABCMeta, flask.Blueprint)):
...
authorized_url = authorized_url or "/{bp.name}/authorized"
...
        self.add_url_rule(
            rule=authorized_url.format(bp=self),
            endpoint="authorized",
            view_func=self.authorized,
        )

It means that authorized_url can only be a view in the current Flask app instance. Which is conformant to OAuth and Slack API and does not let you use this hack.

I am still curious if this section in Slack API app configuration is configured to use https:
image

@aardvark82
Copy link
Author

aardvark82 commented Nov 25, 2018

Yes, https URL's are to be specified in the Slack App Redirect URL's section - , although http URL's can be specified too as valid redirect URL's for local testing.

@iw-an
Copy link

iw-an commented Nov 26, 2018

For anyone else coming across this issue who might be using heroku + cloudflare encountering this issue. Your issue may lay within your Cloudflare settings. SSL mode needs to be set to full so that Cloudflare will forward the request correctly. Without heroku will only see X-Forwarded-Proto: http

@Lexy2
Copy link

Lexy2 commented Dec 1, 2018

@aardvark82,

Digging deeper, I found out that this situation is well-known and mentioned in Flask documentation.

Thus, instead of implementing a custom reverse proxy handler, you could use a nicer code of

from werkzeug.contrib.fixers import ProxyFix

app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app)

@daenney
Copy link
Collaborator

daenney commented Dec 2, 2018

There's a few things here:

  • If you want HTTPS, ensure the request comes in over HTTPS
  • If/when you proxy the request to a backend, ensure you set X-Forwarded-Proto to https and also ensure you set/proxy X-Forwarded-For to preserve the request origin. Ideally you don't force X-Forwarded-Proto to https statically but use a variable set by your httpd that contains the actual protocol it came in with, so you don't accidentally proxy plain text pretending it's secure. In nginx that would be $http_x_forwarded_proto whereas for Apache RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}.

That should be enough for Flask to do the right thing, no need for the ProxyFix at that point. The sample nginx configuration given in Flask's config is what you need.

For Apache there's also mod_remoteip nowadays, to be paired with a setting of RemoteIPHeader X-Forwarded-For to preserve the request origin.

@daenney
Copy link
Collaborator

daenney commented Dec 2, 2018

There's also documentation on our side: https://flask-dance.readthedocs.io/en/latest/proxies.html

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

4 participants