This article assumes you already know what a cloud function is. If you need a refresher, check out other posts on this blog or the official Google documentation. You should also have some basic experience writing and deploying cloud functions to follow along. If you’re new to cloud functions, I suggest starting with the Firebase cloud functions documentation. In my experience, it’s easier to follow than the GCP cloud functions documentation.
Introduction
When it comes to securing a cloud function, there are generally three types of access you need to think about:
- Access for a specific set of users, typically developers.
- Access for other functions, since cloud functions can trigger one another without any human interaction.
- Access for the end users of your product.
In this post, I’ll focus on the third case: giving access to your system’s end users. I’ll talk about the other two cases in a future post.
To secure access for end users, your system needs Firebase Authentication, so only authenticated users can use it. I’ll show you how to let only authenticated users access your cloud functions. This guide focuses on cloud functions triggered by an HTTPS request, but you can adapt the same method for callable cloud functions with a few changes.
Setting Up a Basic Backend Function
First, set up your HTTPS cloud function and open the endpoint you need. I’ll use TypeScript for all the examples here. The example below creates an endpoint that responds with "Hello World" to a POST request.
const functions = require('firebase-functions');
const express = require('express');
const cors = require('cors');
const cookieParser = require('cookie-parser');
import {Request, Response} from 'express';
const app = express();
app.use(cors({ origin: true }));
app.use(cookieParser());
app.post('/', (req: Request, res: Response) => res.send("Hello World\n"));
exports.api = functions.https.onRequest(app);You can deploy the cloud function with npm run deploy. Then run the following cURL command to check if it’s working. Be sure to use your own cloud function URL.
curl --location --request POST 'https://us-central1-fcode-blog.cloudfunctions.net/api'If everything is set up correctly, you should see Hello World printed in the console.
Understanding the Authorization Process
Before we get into the implementation, let’s look at how the authorization process works:
- When the REST API is called from the client side (web or mobile), the client sends a special token called an ID token, which can only be obtained for authenticated users via the Firebase Authentication client SDK or API.
- This is an encrypted key that can be used to identify the user uniquely.
- The server analyzes the key to identify which user is making the request. If no user can be found for that token, it sends an unauthorized response back to the client.
This method of authorizing a user is called bearer token authorization. That’s why we send the ID token in the header as Authorization: Bearer ID_TOKEN.
Applying the Middleware
We can create Express middleware to block any request that doesn’t have a valid authorization header.
const validateToken = async (req: Request, res: Response, next: Function) => {
console.log('Check if request is authorized with Firebase ID token');
}We’ll use this middleware to check the ID token, then add it to the Express app:
const app = express();
app.use(cors({ origin: true }));
app.use(cookieParser());
app.use(validateToken);Validating the ID Token
First, check if the ID token is in the header or cookies. If it’s missing, return an unauthorized error to the client.
if ((!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) &&
!(req.cookies && req.cookies.__session)) {
console.error('No Firebase ID token was passed as a Bearer token in the Authorization header.',
'Make sure you authorize your request by providing the following HTTP header:',
'Authorization: Bearer <Firebase ID Token>',
'or by passing a "__session" cookie.');
res.status(403).send('Unauthorized');
return;
}Next, extract the ID token from the header or the cookie.
let idToken;
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
console.log('Found "Authorization" header');
// Read the ID Token from the Authorization header.
idToken = req.headers.authorization.split('Bearer ')[1];
} else if(req.cookies) {
console.log('Found "__session" cookie');
// Read the ID Token from cookie.
idToken = req.cookies.__session;
} else {
// No cookie
res.status(403).send('Unauthorized');
return;
}Finally, verify that the extracted token is valid using the Firebase Authentication Admin SDK.
const decodedIdToken = await admin.auth().verifyIdToken(idToken);You can find all the properties of the returned object (decodedIdToken) in this documentation. The main ones you’ll need are email, phone_number, auth_time, and uid. These let you uniquely identify the user who made the API call. It’s helpful to store this object on the request so your endpoint can access it.
After all these steps, our middleware looks like this:
const validateToken = async (req: Request, res: Response, next: Function) => {
console.log('Check if request is authorized with Firebase ID token');
if ((!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) &&
!(req.cookies && req.cookies.__session)) {
console.error('No Firebase ID token was passed as a Bearer token in the Authorization header.',
'Make sure you authorize your request by providing the following HTTP header:',
'Authorization: Bearer <Firebase ID Token>',
'or by passing a "__session" cookie.');
res.status(403).send('Unauthorized');
return;
}
let idToken;
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
console.log('Found "Authorization" header');
// Read the ID Token from the Authorization header.
idToken = req.headers.authorization.split('Bearer ')[1];
} else if(req.cookies) {
console.log('Found "__session" cookie');
// Read the ID Token from cookie.
idToken = req.cookies.__session;
} else {
// No cookie
res.status(403).send('Unauthorized');
return;
}
try {
const decodedIdToken = await admin.auth().verifyIdToken(idToken);
console.log('ID Token correctly decoded', decodedIdToken);
req.user = decodedIdToken;
next();
return;
} catch (error) {
console.error('Error while verifying Firebase ID token:', error);
res.status(403).send('Unauthorized');
return;
}
}Now you can read the properties of decodedIdToken in your API. Instead of just sending "Hello World" as the response, you can do the following. Since we’re using the Firebase Admin SDK, remember to import and initialize it:
const admin = require('firebase-admin');
if (admin.apps.length === 0) admin.initializeApp();
app.post('/', (req: Request, res: Response) => res.send(`Hello, ${req.user?.email}.`));Fixing Lint Errors
Since we’re using TypeScript, you can’t just add properties to an existing object. The line req.user = decodedIdToken; will cause an error. To fix this, you need to update the definition of the Express Request class. Create a file named index.d.ts inside a functions/@types/express folder (create the folder if it doesn’t exist), then add the following:
import {Request} from 'express';
import * as admin from "firebase-admin";
import DecodedIdToken = admin.auth.DecodedIdToken;
declare module 'express' {
interface Request {
user?: DecodedIdToken
}
}Next, tell TSLint that you’re overriding some object definitions in your code. Open functions/tsconfig.json and add the following lines under compilerOptions. (Don’t remove the existing key-value pairs; just add these.)
"allowSyntheticDefaultImports": true,
"typeRoots": [
"@types",
"./node_modules/@types"
]Testing the Middleware
After you deploy the new version, try the first cURL command again to see if the API still accepts unauthorized requests. It shouldn’t accept them now.
Next, create a test user in Firebase Authentication. For example, I’ll use the email ramesh@example.com and password 123456789. Then, use the Firebase REST API to log in with those credentials. You’ll need an API key, which you can find in the Project Settings page of the Firebase Console under the Web API Key field. Copy that value and replace API_KEY in the following cURL command.
curl --location --request POST 'https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=API_KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"email": "ramesh@example.com",
"password": "123456789",
"returnSecureToken": true
}'When you run this command, you’ll get a JSON response. The value you need is idToken. Copy it and replace ID_TOKEN in the next cURL command:
curl --location --request POST 'https://us-central1-fcode-blog.cloudfunctions.net/api' \
--header 'Authorization: Bearer ID_TOKEN'After you run the command, you should see the expected output: Hello, ramesh@example.com.
Client-Side Integration
To access this endpoint from the client side, you’ll need to get the ID token for the authenticated user. The testing section above showed how to do this with the Authentication REST API. This section explains how to get it using the Firebase Authentication SDK instead.
- Flutter: Use FirebaseAuth.currentUser to get the authenticated user, then call getIdToken() on the returned user.
- Web: Use Auth.currentUser to get the authenticated user, then call getIdToken() on the returned user.
- Android: Use FirebaseAuth.getCurrentUser() to get the authenticated user, then call getIdToken() on the returned user to get a
Task. Once thatTaskcompletes, call getToken(). - iOS: Use Auth.currentUser to get the authenticated user, then call getIdTokenResult() on the returned user.
Use the ID token you get in the Authorization header, just like in the earlier example.
Conclusion
I covered a lot in this post. I hope I explained each step clearly so you understand why you’re doing it, not just copying code. The key is to understand the reasoning behind each step. Once you do, you can adapt and use these ideas anywhere.
If you have any questions, feel free to leave a comment. I’ve uploaded my code to a GitHub repo, which you can find below.
Authenticating Cloud Functions - This repository contains sample code to secure your cloud functions using Firebase Authentication.


