33

Like this question: Best way to make Django's login_required the default

I'm using Flask-Login's login_required decorator now. Is there anyway to make it the default behavior in Flask?

Community
  • 1
  • 1
yegle
  • 5,795
  • 6
  • 39
  • 61
  • Maybe you can overwrite some function like full_dispatch_request() and do the logic there, then call real view if validation passed. More about this function and other API functions (where you can find the function you need) http://flask.pocoo.org/docs/api/#flask.Flask.full_dispatch_request Or firstly you can try before_request decorator, but not sure if you will have all the data needed in that state. Haven't done this myself, so sorry can't give more precise answer or code example. – Ignas Butėnas Nov 17 '12 at 08:14
  • Can you clarify what you mean by "default behavior" ? Do you mean ALL view functions should have login_required by default ? – codegeek Nov 17 '12 at 17:33
  • @codegeek yes, except for some white-listed views which I can define in my configuration file. – yegle Nov 18 '12 at 04:59
  • @IgnasB. Thank you for your hint. I will check the `full_dispatch_request` function – yegle Nov 18 '12 at 05:01

4 Answers4

42

I did this in my instruments project. I use the before_request decorator:

@app.before_request
def check_valid_login():
    login_valid = 'user' in session # or whatever you use to check valid login

    if (request.endpoint and 
        'static' not in request.endpoint and 
        not login_valid and 
        not getattr(app.view_functions[request.endpoint], 'is_public', False) ) :
        return render_template('login.html', next=request.endpoint)

and I then created an is_public() decorator for the few places that would need to be accessible without login:

def public_endpoint(function):
    function.is_public = True
    return function
MalphasWats
  • 3,255
  • 6
  • 34
  • 40
  • 5
    I think 'static' not in request.endpoint isn't quite right - maybe something like request.endpoint == 'static', or request.endpoint.startswith('static/')? – nonagon Jul 20 '15 at 20:51
  • This solution will `raise KeyError` instead of 404 responses. So, it's good to catch them. – skybobbi Oct 20 '20 at 23:49
  • Please check my answer below why relying on solely checking static keyword in the route might be a security concern. – Kristof Gilicze Feb 15 '21 at 18:23
18

If you are using blueprints and need to protect an entire blueprint with a login, you can make the entire before_request to require login.

This is what I use for my CMS blueprint:

@cms.before_request
@login_required
def before_request():
    if g.user.role != ROLE_ADMIN:
        abort(401)

If you need only to check if the user is logged in (and not if the user has privileges) you can simply pass the function

Richard de Wit
  • 7,102
  • 7
  • 44
  • 54
14

This is a follow up ( bit more pythonic but thats debatable ) to @MalphasWats already great answer.

Also includes an important security fix suggested by @nonagon.

Explanation of the vulnerability with 'static' in request.endpoint:

Imagine that there is route which can be user defiened in some way, like a profile link for example.

If the user sets his name lets say Static Joe, then:

"Static Joe" --slugifys--> /usr/profiles/static_joe.

This way making this route public. This is just asking for trouble.


Here is the route guard function which is appened before every request handling:

@app.before_request
def check_route_access():
    if any([request.endpoint.startswith('static/'),
            current_user.is_authenticated,  # From Flask-Login
            getattr(app.view_functions[request.endpoint],'is_public',False)]):
        return  # Access granted
    else:
        return redirect(url_for('users.login_page'))

( Flask-Login is an excellent module and makes session handling a breeze )

And here is the decorator ( @public_route ) which you can use to allow access to special pages that need public access by default. (register page, login page):

def public_route(decorated_function):
    decorated_function.is_public = True
    return decorated_function
Kristof Gilicze
  • 509
  • 3
  • 7
  • For people who are using `Flask-OIDC` instead of `Flask-Login`, you may also need to add `request.path.startswith('/oidc/callback')` to the condition for granting access – Anuj Kumar Jan 16 '20 at 01:32
  • Good answer but there is a typo in the code - should be current_user.is_authenticated NOT current_user.is_authenticated() – Bill Aug 11 '20 at 16:32
  • Yeah, for sure this was working code. Maybe they updated Flask-Login to use prop instead. – Kristof Gilicze Sep 10 '20 at 19:49
1

I had to secure a REST API and I have solved finally like this:

@app.before_request
@auth.login_required
def login_required_for_all_request():    
    pass  

(Actually I used also the connexion framework so I had to use: @app.app.before_request )

tanacsg
  • 371
  • 5
  • 6