Secure HTTP trigger for Cloud Functions for Firebase - firebase

Is there a way to check if a user is firebase-authorized before triggering a cloud function? (Or within the function)

Yes. You will need to send the Firebase ID token along with the request (for example in the Authorization header of an AJAX request), then verify it using the Firebase Admin SDK. There is an in-depth example in the Cloud Functions for Firebase samples repository. It looks something like this (made shorter for SO post):
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const cors = require('cors')();
const validateFirebaseIdToken = (req, res, next) => {
cors(req, res, () => {
const idToken = req.headers.authorization.split('Bearer ')[1];
admin.auth().verifyIdToken(idToken).then(decodedIdToken => {
console.log('ID Token correctly decoded', decodedIdToken);
req.user = decodedIdToken;
next();
}).catch(error => {
console.error('Error while verifying Firebase ID token:', error);
res.status(403).send('Unauthorized');
});
});
};
exports.myFn = functions.https.onRequest((req, res) => {
validateFirebaseIdToken(req, res, () => {
// now you know they're authorized and `req.user` has info about them
});
});

Since the question asks for auth-based access (1) within, or (2) before a function, here's an method for the "before" case: >
Since every Firebase Project is also a Google Cloud Project -- and GCP allows for "private" functions, you can set project-wide or per-function permissions outside the function(s), so that only authenticated users can cause the function to fire.
Unauthorized users will be rejected before function invocation, even if they try to hit the endpoint.
Here's documentation on setting permissions and authenticating users. As of writing, I believe using this method requires users to have a Google account to authenticate.

Related

Authenticate Firebase Storage with Firebase ID Token

I want to have my server-side as clean of credentials as possible. For that reason, I authenticate myself with Firebase Auth using Firebase Auth Rest Api. From the request, I obtain the firebase ID Token and with this token, I make requests to Firebase Realtime Database as Authenticate Rest Request (Authenticate with an ID token) explains.
The problem is that when I try to do the same with Firebase Storage (Google Cloud Storage Request Endpoints) I don´t find any solution avoiding credentials storing in server-side (for example, with Admin SDK I can write or read any file, but this means a possible future issue with security in my server because my credentials are exposed) despite in Authenticate Rest Request (Authenticate with an ID token) clearly says: "When a user or device signs in using Firebase Authentication, Firebase creates a corresponding ID token that uniquely identifies them and grants them access to several resources, such as Realtime Database and Cloud Storage."
The question is: how can I use the Firebase ID Token to authorize Firebase Storage Api Rest calls as I did with Firebase Runtime Database?
Thanks.
Finally I found a solution to my answer. The solution is to use Cloud Functions.
Cloud Functions allows us to create endPoints and to use AdminSdk in a nodejs enviroment which is part of our firebase project. With this approach we can send an http request to that endpoint, this checks if the received Token with the request is valid and if it is, it saves the file.
This is the functions code:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const path = require("path");
const os = require("os");
const fs = require("fs");
const Busboy = require("busboy");
// Follow instructions to set up admin credentials:
// https://firebase.google.com/docs/functions/local-emulator#set_up_admin_credentials_optional
admin.initializeApp({
credential: admin.credential.cert(
__dirname + "/path/to/cert.json"
),
storageBucket: "bucket-name",
});
const express = require("express");
const app = express();
// Express middleware that validates Firebase ID Tokens passed in the Authorization HTTP header.
// The Firebase ID token needs to be passed as a Bearer token in the Authorization HTTP header like this:
// `Authorization: Bearer <Firebase ID Token>`.
// when decoded successfully, the ID Token content will be added as `req.user`.
const authenticate = async (req, res, next) => {
if (
!req.headers.authorization ||
!req.headers.authorization.startsWith("Bearer ")
) {
res.status(403).send("Unauthorized");
return;
}
const idToken = req.headers.authorization.split("Bearer ")[1];
try {
const decodedIdToken = await admin.auth().verifyIdToken(idToken);
req.user = decodedIdToken;
next();
return;
} catch (e) {
res.status(403).send("Unauthorized");
return;
}
};
app.use(authenticate);
// POST /api/messages
// Create a new message, get its sentiment using Google Cloud NLP,
// and categorize the sentiment before saving.
app.post("/test", async (req, res) => {
const busboy = new Busboy({ headers: req.headers });
const tmpdir = os.tmpdir();
// This object will accumulate all the fields, keyed by their name
const fields = {};
// This object will accumulate all the uploaded files, keyed by their name.
const uploads = {};
// This code will process each non-file field in the form.
busboy.on("field", (fieldname, val) => {
// TODO(developer): Process submitted field values here
console.log(`Processed field ${fieldname}: ${val}.`);
fields[fieldname] = val;
});
const fileWrites = [];
// This code will process each file uploaded.
busboy.on("file", (fieldname, file, filename) => {
// Note: os.tmpdir() points to an in-memory file system on GCF
// Thus, any files in it must fit in the instance's memory.
console.log(`Processed file ${filename}`);
const filepath = path.join(tmpdir, filename);
uploads[fieldname] = filepath;
const writeStream = fs.createWriteStream(filepath);
file.pipe(writeStream);
// File was processed by Busboy; wait for it to be written.
// Note: GCF may not persist saved files across invocations.
// Persistent files must be kept in other locations
// (such as Cloud Storage buckets).
const promise = new Promise((resolve, reject) => {
file.on("end", () => {
writeStream.end();
});
writeStream.on("finish", resolve);
writeStream.on("error", reject);
});
fileWrites.push(promise);
});
// Triggered once all uploaded files are processed by Busboy.
// We still need to wait for the disk writes (saves) to complete.
busboy.on("finish", async () => {
await Promise.all(fileWrites);
// Process saved files here
for (const file in uploads) {
admin.storage().bucket().upload(uploads[file], function(err, file) {
if (err) {
res.status(403).send("Error saving the file.");
}
res.status(201).send("Saved");
});
}
});
busboy.end(req.rawBody);
});
exports.api = functions.https.onRequest(app);
If you need to access Firebase Storage on the server side, then you won't be able to avoid storing credentials somewhere. The only thing you can do is to pass user credentials that were obtained on the client to your server with an API request. Though this won't give you any security benefits because if the attacker has got access to your server, then he would be able to access your storage anyway.
In general, it is safe to store storage credentials on the server. You need to make your server as secure as possible anyway.
Since Firebase Storage is actually just a repacking of Cloud Storage, you can use the Cloud Storage JSON API to work with content in a storage bucket. Start with the section on user account credentials. You will need to provide an OAuth token for the Firebase Auth user to send with the request.
Since for the moment there are no solutions for the question, I decide to store files in binary format in the Realtime Database.
In this way I don´t need to expose my credentials in the server because the Firebase ID Token does the authentication.

Why can I call firestore from a firebase function using onRequest, but not when using onCall

I have two firebase functions deployed, one uses functions.https.onRequest (companyRequest) and one uses functions.https.onCall (companyCall). Both do the exact same thing: retrieve a document from firestore (the exact same document from the exact same collection).
Here are the functions:
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
admin.initializeApp(functions.config().firebase);
export const companyRequest = functions.https.onRequest((request, response) => {
const companyId = "VpOVFo82m9ls3aIK7dra";
admin
.firestore()
.collection("company")
.doc(companyId)
.get()
.then(result => {
response.send(result.data());
})
.catch(error => {
response.send(error);
});
});
export const companyCall = functions.https.onCall((data, context) => {
if (context && context.auth) {
console.log(
"AUTH",
context.auth.uid,
context.auth.token.email
);
}
const companyId = "VpOVFo82m9ls3aIK7dra";
admin
.firestore()
.collection("company")
.doc(companyId)
.get()
.then(result => {
return result.data();
})
.catch(error => {
return error;
});
});
I call companyRequest with curl and it works:
> curl https://us-central1-xxxxx.cloudfunctions.net/company
{"name":"Duny Comp."}
I call companyCall from flutter and it fails (on firebase, server site):
Future click() async {
final HttpsCallable callable = CloudFunctions.instance.getHttpsCallable(
functionName: 'companyCall',
);
HttpsCallableResult resp = await callable.call(<String, dynamic>{
'companyId': 'VpOVFo82m9ls3aIK7dra',
});
print(resp.data);
}
the error I get for companyCall is this:
AUTH 3gvfgYJpk2gIgUpkOLxdkdId uuuu#xxxx.com
ERROR: Error: Could not load the default credentials. Browse to https://cloud.google.com/docs/authentication/getting-started for more information
The error seems quite clear, but why does the unauthenticated call with curl work, but companyCall with firebase authentication via flutter does have a permission problems? In the output you can even see the auth information from the enduser in the flutter app, so he is authenticated.
The question is, why there is a difference between the two? The proposed solutions like Error: Could not load the default credentials (Firebase function to firestore) also feel very strange...
Update:
This is not the same question as in Firebase Cloud Functions: Difference between onRequest and onCall, in this question I ask why there is a difference in the security behaves different between the two different methods. Why do I need to authenticate with an admin account to access the same collection from onCall as I don't need it when access the collection from a onRequest method?

Sync data between Google Firestore and Google Sheets using Cloud Functions/Admin SDK

While using Cloud Firestore as data backend, I need to share some data collections with non-tech site managers (editors, sales teams, etc.). Also, I wish to give these people access to edit the data stored in Cloud Firestore.
Google Sheets is a very familiar tool with site managers which can save me time in developing a CRUD admin panel like the interface from scratch for data updating and viewing.
This Stack Overflow answer shows how to send data using cloud function and levels deep, and this Github library can get data from Firestore using Google Apps Script (I wish to do it using Cloud Functions or Firebase Admin SDK), but I am still trying to figure out how to make an end-to-end Sheets based interface.
Please guide if there are any better alternatives to achieve the same objective. I'm facing some difficulties switching from SQL databases and Django auto-created admin interfaces to the Firebase-Firestore NoSQL world.
I understand that you want to be able to call a Cloud Function from a Google Sheet in order to build an "end-to-end Sheets based interface" for Firestore.
You can use the UrlFetchApp Class to make a request to fetch the URL of an HTTP Cloud Function.
You Apps Script code would be like:
function callSimpleHTTPCloudFunction() {
const url = "https://xxxxxxxx.cloudfunctions.net/simpleHttp";
response = UrlFetchApp.fetch(url, {
method: 'get'
})
respObj = JSON.parse(response.getContentText());
Logger.log(respObj);
}
While your Cloud Function would be like:
exports.simpleHttp = functions.https.onRequest((req, res) => {
res.send({ msg: 'simpleHttp' });
});
This is a very simple example of Cloud Function, but you can adapt this Cloud Function to read and write data from/to Firestore. Have a look at this official video for a starting point: https://www.youtube.com/watch?v=7IkUgCLr5oA&t=1s&list=PLl-K7zZEsYLkPZHe41m4jfAxUi0JjLgSM&index=3
Now, if you want to authenticate your users in such a way you can control who can access your data through the Cloud Function, it is going to be a bit more complex.
There is an official Cloud Function Sample which shows "how to restrict an HTTPS Function to only the Firebase users of your app": https://github.com/firebase/functions-samples/tree/master/authorized-https-endpoint
As explained in the code comments: "The Firebase ID token needs to be passed as a Bearer token in the Authorization HTTP header like this: Authorization: Bearer <Firebase ID Token>. When decoded successfully, the ID Token content will be added as req.user."
So you need, in your Apps Script code, to generate a Firebase ID Token for the Firebase user. For that we will use the Firebase Auth REST API. In this example we will use the email of the user authenticated in the Google Sheet (Session.getActiveUser().getEmail()) as the Firebase User Name.
As explained in the doc, to call the Firebase Auth REST API, you need to obtain a Web API Key for your Firebase project, through the project settings page in your Firebase admin console.
The following Apps Script function will do the job:
function getToken() { {
const userName = Session.getActiveUser().getEmail();
const pwd = 'xyz' //For example get the password via a prompt.
//This is NOT the password of the account authenticated with Google Sheet, but the password of the Firebase user. In this example, the emails are the same but they are different accounts.
const verifyPasswordUrl = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=[API_KEY]" //Replace with your Web API Key
const payload = JSON.stringify({"email":userName,"password": pwd,"returnSecureToken": true});
const verifyPasswordResponse = UrlFetchApp.fetch(verifyPasswordUrl, {
method: 'post',
contentType: 'application/json',
muteHttpExceptions: true,
payload : payload
});
const token = JSON.parse(verifyPasswordResponse.getContentText()).idToken;
return token;
}
Then, still in Apps Script, you use the token in the call to the Cloud Function, as follows:
function callSecuredHTTPCloudFunction() {
const authHeader = {"Authorization": "Bearer " + getToken()};
const url = "https://us-central1-<yourproject>.cloudfunctions.net/securedHttp/";
const response = UrlFetchApp.fetch(url, {
method: 'get',
headers: authHeader,
muteHttpExceptions: true,
});
Logger.log(response);
//Here do what you want with the response from the Cloud Function, e.g. populate the Sheet
}
The Cloud Function code would be as follows, adapted from the official example.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const cors = require('cors')({
origin: true
});
const express = require('express');
const cookieParser = require('cookie-parser')();
const app = express();
// Express middleware that validates Firebase ID Tokens passed in the Authorization HTTP header.
// The Firebase ID token needs to be passed as a Bearer token in the Authorization HTTP header like this:
// `Authorization: Bearer <Firebase ID Token>`.
// when decoded successfully, the ID Token content will be added as `req.user`.
const validateFirebaseIdToken = (req, res, next) => {
console.log('Check if request is authorized with Firebase ID token');
if (
!req.headers.authorization ||
!req.headers.authorization.startsWith('Bearer ')
) {
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>'
);
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];
console.log(idToken);
} else {
// No cookie
res.status(403).send('Unauthorized');
return;
}
admin
.auth()
.verifyIdToken(idToken)
.then(decodedIdToken => {
console.log('ID Token correctly decoded', decodedIdToken);
req.user = decodedIdToken;
return next();
})
.catch(error => {
console.error('Error while verifying Firebase ID token:', error);
res.status(403).send('Unauthorized');
});
};
app.use(cors);
app.use(cookieParser);
app.use(validateFirebaseIdToken);
app.get('/', (req, res) => {
res.send(`Your email is ${req.user.email}`);
});
// This HTTPS endpoint can only be accessed by your Firebase Users.
// Requests need to be authorized by providing an `Authorization` HTTP header
// with value `Bearer <Firebase ID Token>`.
exports.securedHttp = functions.https.onRequest(app);
You can very well write a similar function with a POST and a payload in order to send data from the Google Sheet to the Cloud Function and then write to Firestore.
Finally, note that you could implement the same approach for calling, from the Google Sheet, the Firestore REST API instead of calling Cloud Functions.

Forwarding auth token through API

My team is in the process of migrating away from directly read/writes to and from firebase RTDB for our mobile and web app, to a more classic server/client model.
In doing so, I wanted to see if there was a mechanism to forward a users firebase auth token through our server API into the call to the DB. The purpose of this is so that my security rules could apply to the auth token of the user request, and I wouldn't have to write a layer to manage user data access, instead relying on firebase to handle it for me.
So you want to firebase to check before user accessing the data. In that case, you can use firebase getIdToken like below
firebase.auth().currentUser.getIdToken(); // which returns promise.
attach this token to the http headers and then in API Call check the token like below
const validateFirebaseIdToken = (request, response, next) => {
cors(request, response, () => {
if (!request.headers || !request.headers.authorization) {
return response.status(403).send("Sorry! You're not authorized to access this url");
}
const token = request.headers.authorization.split('Bearer ')[1];
return firebase.auth().verifyIdToken(token).then(decodedId => {
request.user = {};
request.user.uid = decodedId.uid;
return next();
}).catch(error => {
console.log(error);
return response.status(403).send("Sorry! You're not authorized to access this url");
});
});
}
This is how you need to check the firebase id token with the API call. Hope this gives you an idea. Feel free to ask if you any doubts

How to protect firebase Cloud Function HTTP endpoint to allow only Firebase authenticated users?

With the new firebase cloud function I've decided to move some of my HTTP endpoint to firebase.
Everything works great... But i have the following issue. I have two endpoints build by HTTP Triggers (Cloud Functions)
An API endpoint to create users and returns the custom Token
generated by Firebase Admin SDK.
An API endpoint to fetch certain user details.
While the first endpoint is fine, but for my second end point i would want to protect it for authenticated users only. meaning someone who has the token i generated earlier.
How do i go about solving this?
I know we can get the Header parameters in the cloud function using
request.get('x-myheader')
but is there a way to protect the endpoint just like protecting the real time data base?
There is an official code sample for what you're trying to do. What it illustrates is how to set up your HTTPS function to require an Authorization header with the token that the client received during authentication. The function uses the firebase-admin library to verify the token.
Also, you can use "callable functions" to make a lot of this boilerplate easier, if your app is able to use Firebase client libraries.
As mentioned by #Doug, you can use firebase-admin to verify a token. I've set up a quick example:
exports.auth = functions.https.onRequest((req, res) => {
cors(req, res, () => {
const tokenId = req.get('Authorization').split('Bearer ')[1];
return admin.auth().verifyIdToken(tokenId)
.then((decoded) => res.status(200).send(decoded))
.catch((err) => res.status(401).send(err));
});
});
In the example above, I've also enabled CORS, but that's optional. First, you get the Authorization header and find out the token.
Then, you can use firebase-admin to verify that token. You'll get the decoded information for that user in the response. Otherwise, if the token isn't valid, it'll throw an error.
As also mentioned by #Doug,
you can use Callable Functions in order to exclude some boilerplate code from your client and your server.
Example callable function:
export const getData = functions.https.onCall((data, context) => {
// verify Firebase Auth ID token
if (!context.auth) {
return { message: 'Authentication Required!', code: 401 };
}
/** This scope is reachable for authenticated users only */
return { message: 'Some Data', code: 200 };
});
It can be invoked directly from you client like so:
firebase.functions().httpsCallable('getData')({query}).then(result => console.log(result));
The above methods authenticate the user using logic inside the function, so the function must be still be invoked to do the checking.
That's a totally fine method, but for the sake of comprehensivity, there is an alternative:
You can set a function to be "private" so that it can't be invoked except by registered users (you decide on permissions). In this case, unauthenticated requests are denied outside the context of the function, and the function is not invoked at all.
Here are references to (a) Configuring functions as public/private, and then (b) authenticating end-users to your functions.
Note that the docs above are for Google Cloud Platform, and indeed, this works because every Firebase project is also a GCP project. A related caveat with this method is that, as of writing, it only works with Google-account based authentication.
In Firebase, in order to simplify your code and your work, it's just a matter of architectural design:
For public accessible sites/contents, use HTTPS triggers with Express. To restrict only samesite or specific site only, use CORS to control this aspect of security. This make sense because Express is useful for SEO due to its server-side rendering content.
For apps that require user authentication, use HTTPS Callable Firebase Functions, then use the context parameter to save all the hassles. This also makes sense, because such as a Single Page App built with AngularJS -- AngularJS is bad for SEO, but since it's a password protected app, you don't need much of the SEO either. As for templating, AngularJS has built-in templating, so no need for sever-side template with Express. Then Firebase Callable Functions should be good enough.
With the above in mind, no more hassle and make life easier.
There is a lot of great information here that really helped me, but I thought it might be good to break down a simple working example for anyone using Angular attempting this for the first time. The Google Firebase documentation can be found at https://firebase.google.com/docs/auth/admin/verify-id-tokens#web.
//#### YOUR TS COMPONENT FILE #####
import { Component, OnInit} from '#angular/core';
import * as firebase from 'firebase/app';
import { YourService } from '../services/yourservice.service';
#Component({
selector: 'app-example',
templateUrl: './app-example.html',
styleUrls: ['./app-example.scss']
})
export class AuthTokenExample implements OnInit {
//property
idToken: string;
//Add your service
constructor(private service: YourService) {}
ngOnInit() {
//get the user token from firebase auth
firebase.auth().currentUser.getIdToken(true).then((idTokenData) => {
//assign the token to the property
this.idToken = idTokenData;
//call your http service upon ASYNC return of the token
this.service.myHttpPost(data, this.idToken).subscribe(returningdata => {
console.log(returningdata)
});
}).catch((error) => {
// Handle error
console.log(error);
});
}
}
//#### YOUR SERVICE #####
//import of http service
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Observable } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class MyServiceClass {
constructor(private http: HttpClient) { }
//your myHttpPost method your calling from your ts file
myHttpPost(data: object, token: string): Observable<any> {
//defining your header - token is added to Authorization Bearer key with space between Bearer, so it can be split in your Google Cloud Function
let httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
})
}
//define your Google Cloud Function end point your get from creating your GCF
const endPoint = ' https://us-central1-your-app.cloudfunctions.net/doSomethingCool';
return this.http.post<string>(endPoint, data, httpOptions);
}
}
//#### YOUR GOOGLE CLOUD FUNCTION 'GCF' #####
//your imports
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const cors = require('cors')({origin: true});
exports.doSomethingCool = functions.https.onRequest((req, res) => {
//cross origin middleware
cors(req, res, () => {
//get the token from the service header by splitting the Bearer in the Authorization header
const tokenId = req.get('Authorization').split('Bearer ')[1];
//verify the authenticity of token of the user
admin.auth().verifyIdToken(tokenId)
.then((decodedToken) => {
//get the user uid if you need it.
const uid = decodedToken.uid;
//do your cool stuff that requires authentication of the user here.
//end of authorization
})
.catch((error) => {
console.log(error);
});
//end of cors
})
//end of function
})
There is a nice official example on it using Express - may be handy in future: https://github.com/firebase/functions-samples/blob/master/authorized-https-endpoint/functions/index.js (pasted below just for sure)
Keep in mind that exports.app makes your functions available under /app slug (in this case there is only one function and is available under <you-firebase-app>/app/hello. To get rid of it you actually need to rewrite Express part a bit (middleware part for validation stays the same - it works very good and is quite understandable thanks to comments).
/**
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const express = require('express');
const cookieParser = require('cookie-parser')();
const cors = require('cors')({origin: true});
const app = express();
// Express middleware that validates Firebase ID Tokens passed in the Authorization HTTP header.
// The Firebase ID token needs to be passed as a Bearer token in the Authorization HTTP header like this:
// `Authorization: Bearer <Firebase ID Token>`.
// when decoded successfully, the ID Token content will be added as `req.user`.
const validateFirebaseIdToken = async (req, res, next) => {
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;
}
};
app.use(cors);
app.use(cookieParser);
app.use(validateFirebaseIdToken);
app.get('/hello', (req, res) => {
res.send(`Hello ${req.user.name}`);
});
// This HTTPS endpoint can only be accessed by your Firebase Users.
// Requests need to be authorized by providing an `Authorization` HTTP header
// with value `Bearer <Firebase ID Token>`.
exports.app = functions.https.onRequest(app);
My rewrite to get rid of /app:
const hello = functions.https.onRequest((request, response) => {
res.send(`Hello ${req.user.name}`);
})
module.exports = {
hello
}
I have been struggling to get proper firebase authentication in golang GCP function. There is actually no example for that, so I decided to build this tiny library: https://github.com/Jblew/go-firebase-auth-in-gcp-functions
Now you can easily authenticate users using firebase-auth (which is distinct from gcp-authenticated-functions and is not directly supported by the identity-aware-proxy).
Here is an example of using the utility:
import (
firebaseGcpAuth "github.com/Jblew/go-firebase-auth-in-gcp-functions"
auth "firebase.google.com/go/auth"
)
func SomeGCPHttpCloudFunction(w http.ResponseWriter, req *http.Request) error {
// You need to provide 1. Context, 2. request, 3. firebase auth client
var client *auth.Client
firebaseUser, err := firebaseGcpAuth.AuthenticateFirebaseUser(context.Background(), req, authClient)
if err != nil {
return err // Error if not authenticated or bearer token invalid
}
// Returned value: *auth.UserRecord
}
Just keep in mind to deploy you function with --allow-unauthenticated flag (because firebase authentication occurs inside function execution).
Hope this will help you as it helped me. I was determined to use golang for cloud functions for performance reasons — Jędrzej
You can take this as a functions returns boolean. If the user verified or not then you will continue or stop your API. In Addition you can return claims or user result from the variable decode
const authenticateIdToken = async (
req: functions.https.Request,
res: functions.Response<any>
) => {
try {
const authorization = req.get('Authorization');
if (!authorization) {
res.status(400).send('Not Authorized User');
return false;
}
const tokenId = authorization.split('Bearer ')[1];
return await auth().verifyIdToken(tokenId)
.then((decoded) => {
return true;
})
.catch((err) => {
res.status(401).send('Not Authorized User')
return false;
});
} catch (e) {
res.status(400).send('Not Authorized User')
return false;
}
}

Resources