Protect Firebase Cloud Functions From Unauthorized Access

In this tutorial, I’ll show you how you can protect your https cloud functions from unauthorized users in Firebase.

By restricting the unauthorized users, you can save resources and thus decrease the overall cost of your app.

Setting Up The Project

Using the command-line, initialize the Firebase project as follows:

1
2
3
$ mkdir cf-auth
$ cd cf-auth
$ firebase init functions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// output
project
+- .firebaserc # EXISTING - Hidden file that helps you quickly switch between
| # projects with `firebase use`
|
+- firebase.json # EXISTING - Describes properties for your project
|
+- functions/ # NEW - Folder containing all your functions code
|
+- package.json # NEW - The npm package file describing your Cloud Functions
|
+- index.js # NEW - Main source file for your Cloud Functions code
|
+- node_modules/ # NEW - Folder for Cloud Functions dependencies

The setup is usually where you have to select the project name and the language in which you will write your code in. I selected, JavaScript.

Once the setup is complete, cd into the functions directory of the Firebase project.

You’ll see the index.js file where most of your code will reside for this example.

If you want, you can now install express and other middlewares, but I’ll be using plain Node.js.

By default, your index.js will have a commented-out HelloWorld function. Delete it, and create a new function called authorizationHandler.

In each request to our cloud function, we will expect either an authorization header with the Bearer token.

In the absence of both, we will reject the request. Whichever of these are present in our request, we will use the verifyIdToken method from the firebase-admin SDK to check the token validity.

Cloud Functions Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');

admin.initializeApp();

const auth = admin.auth();

const doStuffFromTheRequest = (req, res) => {
res.writeHead(200, {
'Content-Type': 'application/json'
});

return res.end(JSON.stringify({
code: 200,
message: `Hello ${req.user.displayName || req.user.phoneNumber}`,
}));
};


const checkAuthorizationToken = (req, res) => {
if (!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) {
res.writeHead(403, {
'Content-Type': 'application/json',
});

return res.end('Authorization header not present in the request')
}

auth.verifyIdToken(req.headers.authorization.split('Bearer ')[1])
.then((decoded) => {
req.user = decoded;
return doStuffFromTheRequest(req, res);
}).catch((error) => {
res.writeHead(403, {
'Content-Type': 'application/json',
});

return res.end('You are unauthorized to perform this action');
});
};


module.exports = {
api: functions.https.onRequest(checkAuthorizationToken),
};

You can see here, that I’m simply checking if the request headers object has the idToken in the authorization header.

1
if (!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) {/** ... */}

If the authorization header is absent, or if the header doesn’t start with Bearer string, we reject the request by returning from inside the function.

In cases where the request has the authorization header starting with Bearer is present, we simply use the auth.verifyIdToken() method to get the user.

Web Client Code

To make a successful request to the https function, you will need to add the Authorization header to your request.

With the Javascript fetch() API, it is very simple.

1
2
3
4
5
6
7
8
9
10
11
12
13
const url = 'https://us-central1....cloudfunctions.net/api';

fetch(url, {
method: 'GET',
mode: 'no-cors',
headers: {
authorization: 'Bearer ' + getIdToken(),
'Content-Type': 'application/json',
},
}).then((response) => response.json())
// hello `displayName` shoule be the response data
.then((data) => console.log(data))
.catch((error) => handleError(error));

The client code is pretty simple, you just have to set the URL of the request to whatever the cloud-function URL you have in your Firestore project.

The getIdToken() method is the one which you should take a look at. It will return the idToken from the Firebase auth on your client.

On the web client, you can use the auth.onAuthStateChanged() event listener to manage this.

1
2
3
4
5
6
7
8
9
10
11
12
13
const auth = firebase.auth();

auth.onAuthStateChanged((user) => {
if (!user) {
console.log('not logged in');
return;
}
// console.log(user);
user.getIdToken().then((idToken) => {
/** call the fetch function from inside here */
console.log('idToken:', idToken);
});
});