miguelgrinberg / Flask-HTTPAuth

Simple extension that provides Basic, Digest and Token HTTP authentication for Flask routes
MIT License
1.27k stars 228 forks source link

Are there any common strategies for making login_required the default for all routes? #131

Closed ethagnawl closed 3 years ago

ethagnawl commented 3 years ago

First off, this library is great and I was able to get it installed, configured and using custom/conditional behavior with no trouble at all. Thanks for continuing to maintain it and your vast library of resources, @miguelgrinberg!

Is there an anointed approach to applying the login_required decorator to all routes by default? I've done this in the past on a Blueprint-by-Blueprint basis using the before_request filter/hook and, as nice as that is, it still leaves open the possibility of someone forgetting to add it to a new Blueprint, etc. Also, in my experience, the flip side (excepting specific routes) winds up getting complicated and fragile (e.g. @route_security.login_not_required_for(some_bp)).

miguelgrinberg commented 3 years ago

app.before_request will apply to all routes in all blueprints. The same goes for blueprint.before_app_request.

ethagnawl commented 3 years ago

That's ... pretty simple. Thanks.

On the flip side, are you aware of a simple way to except particular routes where they're defined?

miguelgrinberg commented 3 years ago

Your verify_password callback will be invoked with empty username and password when auth isn't provided. You can return True here if the request URL is in your exception list.

ethagnawl commented 3 years ago

I was thinking it'd be nice to do that more dynamically -- without having to maintain a list of exceptions, but what you've described is, essentially, what I've been doing.

For anyone else who comes across this thread searching for a similar solution, here's an implementation of the above:

insecure_views = []

def login_not_required_for(blueprint):
    # DOC: unprotected routes can be configured as follows:
    # @some_bp.route('/foo', methods=["GET"])
    # @route_security.login_not_required_for(some_bp)
    # def foo():
    #     return "foo"

    def decorator(func):
        endpoint = ".".join([blueprint.name, func.__name__])
        insecure_views.append(endpoint)
        return func
    return decorator

@auth.verify_password
def maybe_verify_password(username, password):
    if request.endpoint in insecure_views:
        return True

    if username in users and \
            check_password_hash(users.get(username), password):
        return username