Flask-Login recommends having an is_safe_url() function after user login:
Here is a link to the part of the documentation that discusses this: https://flask-login.readthedocs.io/en/latest/#login-example
They link to this snippet but I don't understand how it implements is_safe_url()
:
https://palletsprojects.com/p/flask/
next = request.args.get('next')
if not is_safe_url(next):
return abort(400)
This doesn't seem to come with Flask. I'm relatively new to coding. I want to understand:
What exactly is happening when request
gets the next
argument?
What does the is_safe_url()
function do to ensure the URL is safe?
Does the next URL need to be checked on login only? Or are there other places and times when it is important to include this security measure?
And most importantly: is there a reliable is_safe_url()
function that I can use with Flask?
Edit: Added link to Flask-Login documentation and included snippet.
As mentioned in the comments, Flask-Login today had a dead link in the documentation (issue on GitHub). Please note the warning in the original flask snippets documentation:
Snippets are unofficial and unmaintained. No Flask maintainer has curated or checked the snippets for security, correctness, or design.
The snippet is
from urllib.parse import urlparse, urljoin
def is_safe_url(target):
ref_url = urlparse(request.host_url)
test_url = urlparse(urljoin(request.host_url, target))
return test_url.scheme in ('http', 'https') and \
ref_url.netloc == test_url.netloc
Now to address your questions:
What exactly is happening when request gets the next argument?
Part of the code we are focusing on here is
next = request.args.get('next')
return redirect(next or url_for('dashboard'))
which redirects user to dashboard (e.g. after successful login) by default. However, if user tried to reach for e.g. endpoint profile
and wasn't logged in we would want to redirect him to the login page. After logging in default redirect would redirect user to dashboard
and not to profile
where he intended to go. To provide better user experience we can redirect user to his profile page by building URL /login?next=profile
, which enables flask to redirect to profile
instead of the default dashboard
.
Since user can abuse URLs we want to check if URL is safe, or abort otherwise.
What does the is_safe_url() function do to ensure the URL is safe?
The snippet in question is a function that ensures that a redirect target will lead to the same server.
Does the next URL need to be checked on login only? Or are there other places and times when it is important to include this security measure?
No, you should check all dangerous URLs. Example of safe URL redirect would be redirect(url_for('index'))
, since its hardcoded in your program. See examples of safe and dangerous URLs on Unvalidated Redirects and Forwards - OWASP cheatsheet.
And most importantly: is there a reliable is_safe_url() function that I can use with Flask?
There is Django's is_safe_url() bundled as a standalone package on pypi.
The accepted answer covers the theory well. Here is one way to deploy a 'safe URL' strategy throughout your Flask project, involving no additional libraries:
util.py:
def is_safe_url(target):
ref_url = urlparse(request.host_url)
test_url = urlparse(urljoin(request.host_url, target))
return test_url.scheme in ('http', 'https') and ref_url.netloc == test_url.netloc
def get_redirect_target():
for target in request.values.get('next'), request.args.get('next'):
if not target:
continue
if is_safe_url(target):
return target
def redirect_back(endpoint, **values):
target = request.form['next'] if request.form and 'next' in request.form else request.args.get('next')
if not target or not is_safe_url(target):
target = url_for(endpoint, **values)
return redirect(target)
example routes.py (one of many):
from util import get_redirect_target, redirect_back
@bp.route('/routepath', methods=['GET', 'POST'], strict_slashes=False)
@login_required
def my_route():
# use these styles (both or separately) as needed
if not (some_condition):
return redirect_back('someother_route')
return_url = get_redirect_target() or url_for('another_route')
...
return redirect(return_url)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With