I think that there is no need to explain what a cloud function is. If you need to know more about cloud functions, there are some articles in this bloc and documentation provided by google will also really help. For proceed with this article you might need to have a little knowledge about writing and deploying cloud functions. If you are new to cloud functions, take a look at the cloud function documentation from Firebase. I find it really easy compared to the GCP cloud function documentation.
When talking about securing a cloud function, there are 3 types of protection that you need to worry about.
In this blog, I would like to talk about giving access to the end users of the system. I will discuss the other ways of securing the cloud function in another blog.
The system needs to have Firebase Authentication for securing the access. Users can use the system only if they are authenticated. Here I will discuss how to give cloud function access (authorize) only for the users who have authenticated in the system. I will focus on cloud functions that can be triggered with HTTPS request. But you can do some modifications and use it for callable cloud functions also.
First you need to set up your HTTPS cloud function. Then you need to open the required endpoint. I will use typescript for the examples. The following example will open an endpoint and upon a POST request, it will return "Hello World" as the response.
You can deploy the cloud function with npm run deploy. Then you can run the following cURL command to verify whether it is working. Make sure to change the request URL to your cloud function.
You will see Hello World text in the console if everything is correct.
Without going into the next step, I will explain how authorization is done.
This way of authorizing a user is called bearer token authorization. Therefore we send this ID token in the header as Authorization: Bearer ID_TOKEN.
We can create an express middleware and block any request that doesn’t have a valid authorization header.
We can use the above middleware to validate the ID token. Then add that middleware to the express object.
First we need to check if the ID token is there in the header or in the cookies. If not we can return an unauthorized error back to the client.
Then we can extract the id token from the header or from the cookie.
Finally, we need to verify the extracted token is a valid one. For that we use Firebase Authentication Admin SDK.
All the properties of the returning object (named as decodedIdToken) can be found in this documentation. Main properties that you will need are email, phone_number, auth_time and uid. From these details, you can uniquely identify the user who made this API call. For that reason, you may need to store this object in the request so that end point implementation can access it.
After all these steps, our middleware will look like this.
Then we can read the properties of the decodedIdToken within our API. Without just sending "Hello World" as the success response, we can do the following. Also as we are using firebase admin SDK, we need to import and initialize that as well.
Since we are using typescript, you can’t just add properties to a already defined objects. In that case req.user = decodedIdToken; line will give you an error. Therefore we need a trick to change the definition of the express Request class. Create a file named index.d.ts in functions/@types/express folder. You might need to create the folder. Then add the following.
Then we have to tell TSLint that we are overriding some of the object definitions within our code. To do that, open functions/tsconfig.json and add the following lines under compilerOptions. (Do not remove the existing key-values. Just add these.)
After you deploy the new version, try the first cURL command to check whether this API still accepting unauthorized requests. You will see that it is not.
Then you need to create a test user in the Firebase Authentication server. I will create a user with email firstname.lastname@example.org and password 123456789. Then use the firebase REST API to login with email and password. To use firebase REST API, you will need a API key. You can get that in the Project Settings page under Web API Key field in the Firebase Console. Copy that value and replace API_KEY in the following cURL command.
When you run the above command you will get a json object as the response. The value we are interested in is the idToken of the response. Copy that and replace ID_TOKEN of the following cURL command with it.
After you execute the cURL command, you will see the intended output as Hello, email@example.com.
When we are accessing this end point using the client side, we need to get the ID_TOKEN for authenticated users. You saw how to get that if you are using the Authentication REST API from the above testing section. This section will describe how to get it from the Firebase Authentication SDK.
In flutter SDK you can use FirebaseAuth.currentUser to get the authenticated user. Then call the getIdToken() method of the returned user.
In web SDK you can use Auth.currentUser to get the authenticated user. Then call the getIdToken() method of the returned user.
In android SDK you can use FirebaseAuth.getCurrentUser() to get the authenticated user. Then call the getIdToken() method of the returned user to get a Task. After that Task is completed, call the getToken() method.
In iOS SDK you can use Auth.currentUser to get the authenticated user. Then call getIdTokenResult() method of the returned user.
Use the obtained ID token in the Authorization header.
I think I covered lot of things in this blog. I hope I explained every step very well so that you know why you did it, instead of just copying and paste internet code. Most important thing is to understand why I did those things in each steps. Then you can modify and apply that knowledge into anything.
Anyway, if you have any more questions, feel free to add a comment. I pushed my code into a github repo. You can go to that with the following link.