How do I get a is_safe_url() function to use with Flask and how does it work?

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)