Forwarding auth token through API - firebase

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

Related

What is the best way to send firebase user token from frontend to backend in API Auth header without having to fetch token each time ? (NextJs))

I need to send firebase user token in API authorization header in each API request in my next.js app.
Current Implementation:
axios.interceptors.request.use(
async (config) => {
const token = await firebase.auth().currentUser?.getIdToken(true);
if (token) {
config.headers["Authorization"] = "Bearer " + token;
}
return config;
},
(error) => {
return Promise.reject(error);
}
)
But the issue with this implementation is to User Token from firebase is fetched before each request which is effecting the performance of the applications.
Is there a better way to implement it ? I am thinking to store the token in local storage and get token from localStorage instead of firebase.
firebase.auth().onAuthStateChanged(async (user) => {
try {
if (user) {
const token = await user.getIdToken(true);
await localStorage.setItem("app_session", token);
}
} catch (err) =>{
// Handle error
}
}
And use it like this
axios.interceptors.request.use(
async (config) => {
const token = localStorage.getItem("app_session");
if (token) {
config.headers["Authorization"] = "Bearer " + token;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
There is a lot written on the subject of how best to store authentication tokens in frontend applications. There are several solutions and each has its pros and cons. This previous answer might give you some ideas.
This article gives a nice overview and explains why storing in memory might be the best option. Here is another article with a similar conclusion
In your case, you might store the token in memory. When required, check for its existence, and if it is not in memory (think browser refresh) make the call to Firebase to retrieve the token and store it in memory again.
Of course, you should look at your specific context and needs and decide if any of the other solutions, such as localStorage, might work for you and your security requirements.

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.

firebase auth share session/token between microservices

I working with firebase auth and I'm a bit confused on how to manage the authentication between different apps, in particular nextjs (main site, that use express too) api and react app (is a kind of dashboard). After reading a bit of documentation what I did (but I'm not sure is the right way) is to get the idToken on the main site with the client side library:
firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then(() => {
const currentUser = firebase.auth().currentUser.getIdToken();
currentUser.then(idToken => {
return axios.post("/auth/login", { idToken });
});
});
and make a request to nextjs/express to create the cookie sessions:
firebase
.auth()
.createSessionCookie(idToken, { expiresIn })
.then(
sessionCookie => {
res.cookie("session", sessionCookie, {
maxAge: expiresIn,
httpOnly: true
});
return res.end(JSON.stringify({ status: "success" }));
},
error => res.status(401).send(error)
);
then when I need to send a request to the api I'll pass the idtoken saved in the cookie and I verify the token in a middleware
const userInfo = await firebase.auth().verifySessionCookie(authToken);
I'm not implemented the react app yet but I think in that I'll just use the clientside library to do everything....My main doubt is the authentication between the nextjs/express and the api, I'm not sure if usin the sessioncookie is the right choise...do I need to send just the tokenId instead of the session cookie? do you have any suggestions?

Secure HTTP trigger for Cloud Functions for 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.

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