Google Cloud Functions are a great way to glue together functionality between client apps and cloud based tools. Cloud Functions are snippets of code (in our case python) that are automatically spun up and run in the cloud and are triggered by a web request (among other methods). For example, say I wanted to update a database that lives in my Google Cloud Project, I could just send the data to a URL which would activate my python cloud function which could unpack and process the data.
No need to worry about managing servers or scaling, this is all handled by the Cloud Function service, you just POST to a URL and everything "magically" happens. They are super useful when it's not totally necessary to build out a complete REST API, but you still need a few endpoints to run server-side. This is great, but how do we stop just anyone from accessing (or worse, updating!) our database?
Google Cloud Functions
A google cloud functions are just a snippet of Python code that are passed a Flask Request object. This is purely for convenience as the the request object essentially just holds the data sent to the URL (e.g. JSON).
Conventionally, along with the payload, a token can be passed inside the header which can be used to validate users. We use the authentication functionality offered by Firebase (recently acquired by Google) to allow users to sign in using an email/password combination. For the sake of this post, assume that a user has logged in and they have access to their token (client side), which is just a string of characters (you can read more about and play with some tokens here).
This token rides along with our HTTP request in the header section. We can modify our function to make sure that the user is valid by checking that header value:
Thanks to some great library support, its pretty straightforward to check if a user has a valid token in the headers of the request object. However, we would need to copy and past this code every time we wanted to validate a user or would need to write a separate function to do do the validation, which can get a little messy.
What we are trying to express here is that there are certain functions that can only be executed by authorized users. This behavior is more a property of the function than a result, side effect, or product of it. Sounds like a perfect case for a python decorator (see this great review on python decorators).
Python decorators are often used as a way to modify the behavior of a function. We can essentially break our
echo_authenticated_payload function into two separate pieces
authenticated which we can then chain together to achieve the desired outcome. The behavior we are looking for is roughly:
echo(authenticated(request)), where echo only gets called on authenticated requests. Let's write a decorator for our echo function:
authenticated function takes in another function (
fn) and does one of two things. Either it is successful in extracting and authenitcating the token, in which case it returns the result of the original function (
fn), or it returns the result of an
abort call, which just returns an HTTP error code.
This decorator can be applied to any function that has the general function signature of
def function_name(request), where the only parameter is a request object. We could make our decorator more generalized, but that is outside the scope of this blog post. For now, lets just decorate our original
echo function to only accept
All our boilerplate is gone! Well most of it. Our function is now "decorated" with
@authenticated, which means that when the function is called, the code defined in the above
def authenticated function gets evaluated first. This results in a clearly defined function that can only be called by a user with a valid token!