Using Python Decorators for Authentication

Posted on Mon 19 March 2018 in technology

A Refresher

Python decorators are bits of syntactic sugar that allow you to execute complementary code every time a function is executed. Given this function...

def super_useful_decorator(fn):

    def wrapper():
        print "hello world"
        fn()

    return wrapper

...this:

def hello_world():
    return 42

super_useful_decorator(hello_world())()

...is exactly the same as this:

@super_useful_decorator
def hello_world():
    return 42

hello_world()

Who Cares?

At this point, most decorator tutorials wax lyrical about how python functions are first class and then spend a lot of time talking about closures. This is interesting, but not necessarily immediately useful. Here instead, is the cliffnotes version:

Function decorators let you do things automatically when you call a function.

Keeping this in mind, let's move on to an actual use case for decorators.

Authenticating API Calls

Say you're setting up a REST API which requires an authenticated token from the user:

def token_is_valid(token):
    # Replace with a call to your token validation service
    return token=='42'

def authenticate_access(token):
    return token_is_valid(token)

def rest_function_1(token, input_1, input_2)
    rv = {}

    if authenticate_access(token):
        rv['code'] = 200
    else:
        rv['code] = 401
        rv['payload'] = input_1+input_2

    return rv

def rest_function_2(token, input_1, input_2)
    rv = {}

    if authenticate_access(token):
        rv['code'] = 200
    else:
        rv['code'] = 401
        rv['payload'] = input_1*input_2

    return rv

This is perfectly workable code, but there are a couple of problems with it.

  • Boredom: Authentication code has to be written for each REST API call. This is boring for the programmer.
  • Brittleness: Changing the authentication code in any way means that every REST API function has to be altered.
  • Repetition: This follows from the last problem, but bears repetition (heh). Saying the same thing over and over in authentication code is dangerous: sooner or later, you're going to say it wrong and let a bunch of people use your service without authentication.

Python decorators are a pretty elegant solution to this problem:

def token_is_valid(token):
    # No seriously, replace this with your own validation code
    return token=='42'

def authenticate_access(fn):
    def wrapfn(*args, **kwargs):
        rv = {}
        token = args[0]
        if token_is_valid(token):
            rv['code'] = 200
            rv['payload'] = fn(*args, **kwargs)
        else:
            rv['code'] = 401
        return rv

    return wrapfn

@authenticate_access
def rest_function_1(token, input_1, input_2):
    return input_1+input_2

@authenticate_access
def rest_function_2(token, input_1, input_2):
    return input_1*input_2

Let's try executing this:

In [1]: rest_function_1('43', 2, 2)
Out[1]: {'code': 401}

In [2]: rest_function_1('42', 2, 2)
Out[2]: {'code': 200, 'payload': 4}

In [3]: rest_function_2('42', 32, 32)
Out[3]: {'code': 200, 'payload': 1024}

Neat.

As an additional bonus, there is now clear separation of concerns between authentication and whatever it is the REST API functions are supposed to be doing. The code just looks cleaner.

Conclusion

If you are using Python, you should be making heavy use of decorators.

If you want to see what other people have used decorators for, the Python wiki is kind of amazing.