I am a newbie in JavaScript. Recently I have installed firebase admin and kept the code in global scope, database.js. Like this.
import app from 'firebase-admin';
import firebase_key from './firebase_key.js';
app.initializeApp({
credential: app.credential.cert(firebase_key),
databaseURL: "https://study-boss-681fa-default-rtdb.firebaseio.com"
});
console.log('database started');
export default app.database();
Now I want to access this database from my service class. My serice class is
import { getDatabase} from "firebase-admin/database";
const database = getDatabase();
This service class code is throwing the following error.
FirebaseAppError: The default Firebase app does not exist. Make sure you call initializeApp() before using any of the Firebase services.
If you are just using getDatabase() directly then there's a high chance that it's being access before your initializeApp is executed. I would recommend initializing all Firebase services in a single file and importing them wherever required as shown below:
import { initializeApp } from 'firebase-admin/app';
import { getDatabase } from 'firebase-admin/database';
import firebase_key from './firebase_key.js';
const app = initializeApp({
credential: app.credential.cert(firebase_key),
databaseURL: "https://study-boss-681fa-default-rtdb.firebaseio.com"
});
const database = getDatabase(app);
console.log('database started');
export { database };
Now you can import { database } from "..path/to/firebase.js" wherever required.
Related
I am following This Guide to create a Functions project with multiple function files in NodeJS. while this is a good base, it does not show how to properly initiate a firebase app by using const app = admin.initializeApp() in a way that could be reused across the different files. my initial thought was to add the following in index.ts and import app as needed, but this raises a Maximum call stack size exceeded error.
import admin = require('firebase-admin');
export const app = admin.initializeApp();
While searching online I could only find some older posts that don't apply to current versions. can anyone provide a guideline for this?
This looks like a circular dependency issue. You need to initialize Firebase Admin only once at the beginning of index.ts and import other services as needed in other files as shown below:
// index.ts
// Don't export anything from this file.
import { initializeApp } from "firebase-admin/app";
initializeApp({
databaseURL: "<DB_URL>",
storageBucket: "<BUCKET>.appspot.com",
});
export * from "./metric";
// metric.ts
import { https } from "firebase-functions/v1";
import { getFirestore } from "firebase-admin/firestore";
const db = getFirestore();
export const test = https.onRequest(async (req, res) => {
const metricData = await db.collection("metrics").get();
res.json(metricData.docs.map((doc) => doc.data()));
});
The task is to make CRUD operations in Firestore after I make an API call to Cloud Function, which later should trigger a Firestore function to get a set of items in Cards collection.
Here are some rules:
There should be no user authentication needed
It shouldn't need to have a service account with granted permissions
The purpose behind the "rules" is to legitimate operations happening in Cloud Functions as it was an authorized admin itself (Because they are deployed to Firebase safe environment anyways right?). Since the plan is to host the project as a Cloud Function, we should be required to have firebase-admin SDK.
For so far, I tried to implement the same with firebase-functions but it only worked if the rule was not restricted publicly being as:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /Cards/{card}{
allow write,read: if false;
}
}
}
Since this works, but the rule is "insecure" I'd like to do these operations only as "admin" would through Cloud Functions. Here is some code that returns an empty array of documents, even though I have data in it viewable from web GUI.
import { getFirestore } from "firebase-admin/firestore";
import * as admin from "firebase-admin";
const GetCard = (cardID: string)=> {
require("../../path-to-the-file.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
getFirestore()
.collection("Cards")
.get()
.then((cardsSnapshot) => {
cardsSnapshot.forEach((card) => {
console.log("card from collection: ", JSON.stringify(card.data()));
});
});
};
EDIT: the reason why I decided to use adminSDK even though Cloud Functions don't need it was the error I was getting:
Error adding document: [FirebaseError: Missing or insufficient permissions.] {
> code: 'permission-denied',
> customData: undefined,
> toString: [Function (anonymous)]
> }
After running this code:
import { initializeApp } from "firebase/app";
import { collection, getDocs, getFirestore } from "firebase/firestore";
const GetCard = (cardID: string): Promise<Card> => {
const firebaseConfig = {...CONFIGS...};
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
getDocs(collection(db, "Cards"))
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(`${doc.id} => ${doc.data()}`);
});
})
.catch((e) => {
console.error("Error adding document: ", e);
});
};
... without admin SDK using Firebase Functions
Actually Cloud Functions do use the Admin SDK. As such they totally bypass the Firestore security rules: they have full access rights (write & read) to all the collections of your database.
So if I correctly understand, by using Cloud Functions you will fulfill you needs.
Just to be complete, there is a service account for the Cloud Function but it is automatically set-up by the platform, so it is transparent for you.
I am working on an application, where it uses Next.js and Firebase.
I need to implement server-side authentication. Firebase allows connecting the server using Firebase Admin SDK.
const admin = require("firebase-admin");
const serviceAccount = require("../../../../3-firebase/service-account-key/service-account-file.json");
try {
admin.initializeApp({
credential: admin.credential.cert({
client_email: process.env.FIREBASE_CLIENT_EMAIL,
private_key: process.env.FIREBASE_PRIVATE_KEY,
project_id: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
}),
});
console.log("Initialized.");
} catch (error) {
if (!/already exists/u.test(error.message)) {
console.error("Firebase admin initialization error", error.stack);
}
}
const db = admin.firestore();
export { db };
I installed the firebase-admin package using NPM and I setup firebase admin SDK using the above code by creating a separate file called "firebase-admin.js"
import { initializeApp } from "firebase/app";
const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
};
const defaultApp = initializeApp(firebaseConfig);
export default defaultApp;
The above code is the default firebase application setup in a separate file called "firebase.js"
The problem I encountered is I am not able to access the admin firestore. However, I can able to access the default firestore.
What I observed is admin SDK is able to initialize using the credentials (private key). But I don't know why I can't access admin firestore. I mean when i use admin firestore the console gives error of 500 (internal server error)
Here is the error message, when I try to use admin.firestore()
{"error":{"code":"invalid-argument","name":"FirebaseError"}}
versions I am using
"firebase": "^9.6.6",
"firebase-admin": "^10.0.2",
Timely help is much needed
You're importing the initializeApp method from the Firebase Javascript SDK. You should be importing from the Firebase Admin SDK.
import { initializeApp } from "firebase/app";
vs.
import { initializeApp } from "firebase-admin/app";
Using firebase-admin v10.0.2 I was able to successfully access the db adding this in my config.js file, similar to your firebase.js file.
const ServiceAccount = require('../superSecretServiceKeyFile.json');
const app = initializeApp(ServiceAccount);
const { getFirestore } = require('firebase-admin/firestore');
const db = getFirestore(app);
I am try to initialize firebase admin SDK in my project but it is not working. I intend using it in my next js API for admin backend
import {
initializeApp,
applicationDefault,
cert,
} from "firebase-admin/app";
import {
getFirestore,
Timestamp,
FieldValue,
} from "firebase-admin/firestore";
import serviceAccount from "../secret.json";
const app = initializeApp({
credential: cert(serviceAccount),
});
export default app.getFirestore();
I try to use Firebase in my application. The frontend logs the user in using the Web SDK, without any backend. Later, I would like to call some backend APIs. For this reason, I pass the idToken to the backend and try to validate the user as described in the Firebase docs.
When I do the above flow locally using the Firebase Emulator everything works as expected.
When I switch off the Emulator the idToken validation fails with
{
errorInfo: {
code: 'auth/argument-error',
message: 'Firebase ID token has invalid signature. See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.'
},
codePrefix: 'auth'
}
I created a Google hosted Firebase function to check if I can get the idToken validated there. The above setup works when the validation happens within the Google infrastructure.
Based on the above, I think the issue is in my FirebaseApp setup in the API. What that issue might be?
This is my setup.
I define 3 environment variables:
FIREBASE_DB_URL=https://<project-id>.firebaseio.com
FIREBASE_PROJECT_ID=<project-id>
GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
I checked and cat $GOOGLE_APPLICATION_CREDENTIALS prints the correct file.
I initialize Firebase in the API with
import admin from "firebase-admin";
if(admin.apps.length == 0) {
admin.initializeApp({
credential: admin.credential.applicationDefault(),
databaseURL: process.env.FIREBASE_DB_URL,
projectId: process.env.FIREBASE_PROJECT_ID,
});
console.log('Firebase initialized')
} else {
console.warn('Firebase already initialized')
}
and this is the validating code
import { DecodedIdToken } from 'firebase-admin/lib/auth/token-verifier';
import { getAuth } from 'firebase-admin/auth';
import './initializeFirebase';
export default async function needsLoggedInUser(idToken: string): Promise<DecodedIdToken|false> {
try {
return await getAuth().verifyIdToken(idToken)
} catch(err) {
console.error(err)
return false
}
}
I use the above in a NextJS API code as
import { NextApiRequest, NextApiResponse } from 'next'
import { getDatabase } from 'firebase-admin/database';
import 'services/backend/initializeFirebase';
import needsLoggedInUser from 'services/backend/needsLoggedInUser';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
// As an admin, the app has access to read and write all data, regardless of Security Rules
const decodedToken = await needsLoggedInUser(req.body.user)
if(!decodedToken) {
return res.status(403).send("403 Forbidden")
}
/// ... rest of the API
}