Flask: how to register a wrapper to all methods

Viewed 1558

I've been moving from bottle to flask. I'm the type of person that prefers writing my own code instead of downloading packages from the internet if I the code needed is 20 lines or less. Take for example support for Basic authentication protocol. In bottle I could write:

def allow_anonymous():
    """assign a _allow_anonymous flag to functions not requiring authentication"""
    def wrapper(fn):
        fn._allow_anonymous = True
        return fn
    return wrapper


def auth_middleware(fn):
    """perform authentication (pre-req)"""
    def wrapper(*a, **ka):
        # if the allow_anonymous annotation is set then bypass this auth
        if hasattr(fn, '_allow_anonymous') and fn._allow_anonymous:
            return fn(*a, **ka)
        user, password = request.auth or (None, None)
        if user is None or not check(user, password):
            err = HTTPError(401, text)
            err.add_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
            return err
        return fn(*a, **ka)
    return wrapper

...

app = Bottle()
app.install(middleware.auth_middleware)

The above code gave me full support for basic auth protocol for all methods unless explicitly decorated with the @allow_anonymous wrapper. I'm just a beginner with flask. I'm having a hard time accomplishing the bottle-compatible code above in flask without adding dependencies on more python packages or excessive boiler-plate. How is this handled directly and clearly in flask?

2 Answers

You can definitely some of the functionality of flask-httpauth yourself, if you wish :-P

I would think you will need to play some before_request games (not very beautiful), or alternatively call flask's add_url_rule with a decorated method for each api endpoint (or have a route decorator of your own that will do this). The add_url_rule gets a view function that is usually your api endpoint handler, but in your case, will be a wrapped method in a manner very much like the one you gave in the post (auth_middleware).

The gist of it:

from flask import Flask, make_response, request

app = Flask(__name__)

def view_wrapper(fn):
    """
    Create a wrapped view function that checks user authorization
    """
    def protected_view(*a, **ka):
        # if the allow_anonymous annotation is set then bypass this auth
        if hasattr(fn, '_allow_anonymous') and fn._allow_anonymous:
            return fn(*a, **ka)
        # consult werkzeug's authorization mixin
        user, password = (request.authorization.username, request.authorization.password) if request.authorization else (None, None)
        if user is None or not check(user, password):
            err_response = make_response(text, 401)
            err_response.headers['WWW-Authenticate'] = 'Basic realm="%s"' % realm
            return err_response
        return fn(*a, **ka)

    return protected_view


# An endpoint
def hello():
    return 'hello there'

app.add_url_rule('/', 'hello', view_wrapper(hello))

Of course, this can (and should) be further enhanced with Blueprints, etc.

Note #1: this is cribbed from separate answers in SO.

Note #2: this doesnt use blueprints. Again, I'm new to flask and I appreciate that blueprints will help the app scale but one step at a time...

def allow_anonymous(decorated_function):
    decorated_function.is_public = True
    return decorated_function

@app.before_request
def auth_middleware():
    fn = app.view_functions[request.endpoint]
    if hasattr(fn, 'allow_anonymous') and fn.allow_anonymous:
        # anonymous permitted
        return
    elif my_custom_authentication():
        # anonymous not permitted authentication succeeded
        return
    else:
        # anonymous not permitted authentication failed
        err_response = make_response(text, 401)
        err_response.headers['WWW-Authenticate'] = 'Basic realm="%s"' % realm
        return err_response

@app.route('/public_thing')
@allow_anonymous
def public_thing():
   return 'yo'

@app.route('/regular_thing')
def regular_thing():
   return 'if you can read this youre authenticated.'

Related