pallets-eco / flask-security

Quick and simple security for Flask applications
MIT License
635 stars 155 forks source link

Production doesn't login after confirmation but dev server does #829

Closed dadiletta closed 1 year ago

dadiletta commented 1 year ago

When I invite a new user using my local development environment, the confirmation link successfully logs in the new user. However, the exact same software running on my production environment flashes a successful confirmation message but redirects to the login page instead. The only thing that I can think of that has changed is that I upgraded my Nginx service in my production environment. Any idea what setting might be adjusted to fix this situation? Video explanation

jwag956 commented 1 year ago

The default behavior changed in 5.3 - any chance you have an older version on your dev? Look at release notes for details

dadiletta commented 1 year ago

Your help is much appreciated! The requirements.txt and both environments' site-packages reads 5.3.0.

I did find another curious error. I thought to recommend my incoming batch of new users reset their passwords so that they might login but if they try they can an error: Bad Request The referrer header is missing.

This error occurs when resetting new and established accounts only in production and not in dev. Could it be connected to the stuttering confirmation emails?

Thank you kindly.

jwag956 commented 1 year ago

Hmm as mentioned in the release notes - reset and confirm now set the no-referrer header as suggested by OWASP - possibly I interpreted that incorrectly. What is complaining about no-referer ??

You might try downgrading to 5.2.x and see if your issues go away

dadiletta commented 1 year ago

Yes, thanks! The rollback has my service back online, ready for the first day of school. Thanks again.

jwag956 commented 1 year ago

Glad things are working - if you have a chance - could you check your NGINX configuration to see if it explicitly denying requests with no referrer (and maybe any ideas on why?). Note that OWASP best practice is to set the noreferrer header in the case of confirmation and reset links: https://cheatsheetseries.owasp.org/cheatsheets/Forgot_Password_Cheat_Sheet.html#url-tokens

Any additional info would be appreaciated.

dadiletta commented 1 year ago

I've crawled over my nginx sites-available and the .conf but I don't see any referrer policy settings. I'd be happy to try to modify nginx to see if I can catch up to the latest release. Let me know if you'd like me to give that a try. I really appreciate your support.

jwag956 commented 1 year ago

Quick searching shows that $http_referer would be the variable to look for. I guess it's worth making absolutely sure it is NGINX that is throwing that error and not other part of your stack... I am considering adding a config to disable this code (i.e. continue adding the referrer header as before) in an upcoming patch release.

augustomen commented 1 year ago

I ran into this exact issue and I think I know what the root cause is: there's an incompatibility between Flask-Security and Flask-WTF. The latter has a built-in CSRF verification that works differently under HTTPS (current version is 1.1.1) - see protect() at https://github.com/wtforms/flask-wtf/blob/v1.1.1/src/flask_wtf/csrf.py#L256:

        if request.is_secure and current_app.config["WTF_CSRF_SSL_STRICT"]:
            if not request.referrer:
                self._error_response("The referrer header is missing.")

            good_referrer = f"https://{request.host}/"

            if not same_origin(request.referrer, good_referrer):
                self._error_response("The referrer does not match the host.")

That's probably where you are getting your error. I manage to fix it by setting WTF_CSRF_SSL_STRICT to False, but it feels like the wrong approach, since it disables CSRF for all requests, not just password reset.

jwag956 commented 1 year ago

Thanks for the tip - will look into that - SSL_STRICT doesn't disable CSRF - just same-origin validation - but I agree FS shouldn't require that. Those redirects shouldn't be form-driven - so possibly I implemented the no-referrer at the wrong place....