How to secure your HTTP Cloud Functions
Are your HTTP Cloud Functions secure? Do you know if anyone with the URL can access them? Do you know which users are calling your functions?
These were all questions I once made after encountering several HTTP functions in a project that were using the admin SDK to write data to Firestore, but were open, which means that, basically, anyone with the URL had access to write stuff to Firestore.
NOTE: Remember that the admin SDK doesn’t care about security rules 😝.
As a best practice (more like as a mandatory practice), we need to make sure we’re locking our api endpoints so that only the people we want can access them.
Today you’ll learn how to lock your HTTP functions and how to send valid authentication credentials to them using the firebase sdk or angularfire.
The first thing we’ll do is create our function, let’s say for example that we want to create a POST endpoint, where whenever we send some information it will add something to the database.
Let’s say I have a functionality in the platform so that registered users can save their progress in
the courses, and I have an HTTPS function called resetUserProfile()
, and when users call the
function it removes all their progress from Firestore to give them a “Start from scratch” kind of
feel.
The first thing we need to do, is to create the function:
I always like to add a bit of extra validation for the request type, to make sure that only POST requests are accepted (Or GET, or PUT, depends entirely on what you’re building).
After we have the function and ensure that only POST requests are valid, we’re going to get the user’s authorization token, for that we’re going to look inside the headers, get the authorization header, and then extract the token from there:
Here’s what’s going on:
- We’re accessing the headers from our request object.
- Inside the headers we’re looking for the authorization header:
req.headers.authorization?
. - The authorization header looks something like this
'Bearer superLongStringTokenHere'
, so we’re using.split('Bearer ')[1]
to break that into an array where'Bearer '
is the item at index 0, and the token is the item at index 1. - And lastly we’re taking the item at index 1, the token, and assigning it to our
idToken
variable.
Then we perform another sanity check, if the token is not there, we return an authorization error and terminate the function.
So far we’ve validated 2 things, 1) that the requests comes from a POST call, and 2) that the request has the authorization token in the headers.
Now we need to actually make sure that the token is valid, and that it belongs to one of our users.
For that, we’ll use the admin SDK, inside the admin SDK’s auth module, we’ll find a handy function that will do all the heavy listing for us:
The verifyIdToken()
function takes the authorization token and verifies it, returning either an
error or the DecodedIdToken
.
DecodedIdToken
is an interface that has a lot of information, we mostly care about our user’s
information:
The interface has more information, but we mostly care about the uid
if we want to have attach
what happens in the function to the user who performed it.
After that all we’d need to check is, did the token decode successfully? Or were there any issues.
If there were any issues, terminate the function with an error, if the token is there, then we can use the admin SDK again, to check that UI and make sure the user is in our project. (In case someone is generating the token from a different project or app)
Once our function is secure, we need to understand how to call it, for that, we can create a master POST caller in our app’s service, that takes whatever POST call you want to make, gets the token, adds it to the headers, and makes the request.
We’ll do this using AngularFire, assuming we’re in an Ionic/Angular project, if you’re on a different project, check the JavaScript SDK for Firebase, it’s almost identical, since AngularFire is mostly a wrapper on that SDK.
Let’s say we have a data service in angular called data.service.ts
, and it looks like this:
The first thing we need to do, is to import and inject everything we need:
We’re going to be using Auth
to get the user’s token, and the HttpClient
to make the actual
request.
Then, we’ll add a function to generate the auth heathers, this function will get the current user, their token, and return the headers with the proper token.
And lastly, we’ll create a function that makes our HTTP call, it should get the headers ready, and send them to the request.
And that’s it, your function should be secure now, and you have a proper way to call it.
Also, if you’re not going to call the function for multiple places, and only from your app, Firebase also has Callable Cloud Functions which you can call from the SDK and remove A LOT of that code 😃
Do let me know if that’s something that interests you and I’ll write more about it 🔥