So I am using firebase-admin in Next Js. I used environment variables but can't hide the firebase service account keys because they are not defined in server-side on Next JS. So i had to use NEXT_PUBLIC environment variables. And NEXT_PUBLIC environment variables can be accessed and viewed in client side.
This is my firebase-admin file
const firebase = require("firebase-admin");
const { fireStore, getFirestore } = require("firebase-admin/firestore");
import { adminConfig } from "./serviceAccountKey";
if (!firebase.apps.length) {
firebase.initializeApp({
credential: firebase.credential.cert(adminConfig),
});
}
export const db = getFirestore();
export default firebase;
And this is how my config object looks like.
export const adminConfig = {
type: process.env.NEXT_PUBLIC_FIREBASE_TYPE,
project_id: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
private_key_id: process.env.NEXT_PUBLIC_FIREBASE_PRIVATE_KEY_ID,
private_key: process.env.NEXT_PUBLIC_FIREBASE_PRIVATE_KEY,
client_email: process.env.NEXT_PUBLIC_FIREBASE_CLIENT_EMAIL,
client_id: process.env.NEXT_PUBLIC_FIREBASE_CLIENT_ID,
auth_uri: process.env.NEXT_PUBLIC_FIREBASE_AUTH_URI,
token_uri: process.env.NEXT_PUBLIC_FIREBASE_TOKEN_URI,
auth_provider_x509_cert_url:
process.env.NEXT_PUBLIC_FIREBASE_AUTH_PROVIDER_CERT_URL,
client_x509_cert_url: process.env.NEXT_PUBLIC_FIREBASE_CLIENT_CERT_URL,
};
So How do i hide the config data. Or is it alright even if it is public?
I managed to do this in NextJS some time back (hopefully it's still relevant) - try this:
Store your secrets as environment variables in a .env file.
In your next.config.js load the environment variables in publicRuntimeConfig
publicRuntimeConfig: {
PRIVATE_KEY: process.env.FIREBASE_PRIVATE_KEY_ID,
PRIVATE_KEY_ID: process.env.FIREBASE_PRIVATE_KEY
}
Then in your client side React:
import getConfig from 'next/config';
const {publicRuntimeConfig} = getConfig();
const private_key = publicRuntimeConfig.PRIVATE_KEY
const private_key_id = publicRuntimeConfig.PRIVATE_KEY_ID
EDIT: Actually, now that I think about it, I used this method for things that didnt require absolute security like API_URIs.
Secrets stored/accessed like this will still be exposed to inspection in the browser.
For anything that requires securely storing secrets, definitely perform them on server-side.
Related
I'm trying to create a new document when a user signs up for my app.
However, 'exports' is returning "Uncaught (in promise) ReferenceError: exports is not defined".
The code below is handling the function. I do also have an onAuthStateChanged function that switches some logged-in/out elements, although I don't think that could be stopping exports from being defined.
import { createUserWithEmailAndPassword, onAuthStateChanged,
signInWithEmailAndPassword } from "firebase/auth";
import { db, auth } from "./firebase";
import { collection, doc, setDoc, addDoc } from "firebase/firestore";
const signUpForm = document.querySelector('#signup-form');
if (signUpForm) {
signUpForm.addEventListener('submit', (e) => {
e.preventDefault();
//get user info
const email = signUpForm['signup-email'].value;
const password = signUpForm['signup-password'].value;
createUserWithEmailAndPassword(auth, email, password).then((cred) => {
const overlay = document.getElementById('overlay');
overlay.classList.add('hidden');
overlayP.classList.remove('hidden');
signUpForm.reset();
exports.createUserDoc = functions.auth.user().onCreate((user) => {
return admin.firestore().collection("users").doc(user.uid).setDoc({
email: user.email,
uid: user.uid,
})
});
// document.getElementById("signUpErr").innerHTML = "";
})
// .catch(err => {
// document.getElementById("signUpErr").innerHTML = err.message;
// });
});
};
I have initialized firebase and installed express.js within my index.js file but am I missing something to make sure this parameter is defined? I'm using Vite as a package bundler and node.js.
I'm new to coding and firebase, any advice would be massively appreciated.
I managed to solve this issue by taking a few days to read the documentation and understand what is going on with Cloud functions. The benefit of having a cloud function is that you can create triggers to your database that is away from your client-side code, improving security.
I was trying to call the cloud function within my app.js file and not within the firestore functions index.js file created when initializing firebase. Here you import through CommonJS Modules (CJS) the required SDK, in my case it was functions and admin.
Now my cloud functions live within index.js in /Functions folder separate from my app files.
// The Cloud Functions for Firebase SDK to create Cloud Functions and set up triggers.
const functions = require('firebase-functions');
// The Firebase Admin SDK to access Firestore.
const admin = require('firebase-admin');
admin.initializeApp();
I also had to change the .setDoc() function to .set() as this was a Type error. I also added a userDelete function to delete users' documents in firestore.
exports.createUserDoc = functions.auth.user().onCreate(user => {
// Your new auth record will have the uid and email on it because
// you used email as the way to create the auth record
return admin.firestore().collection("users").doc(user.uid).set({
email: user.email,
bookmarked: []
})
});
exports.userDeleted = functions.auth.user().onDelete(user => {
const doc = admin.firestore().collection("users").doc(user.uid);
return doc.delete();
});
I would also like to note that setting up the firestore emulator has been very useful in this process and I'm sure will help me develop and test the other functions I need to create my app.
I have tried to integrate firebase with Nuxt Js and i am getting this error
As per documentation first I have installed firebase with help of "npm install firebase" and then i have installed "npm install #nuxtjs/firebase" and third i have integrated my firebase config in modules in nuxt.config.js
so whats the solution to solve the above error?
Thanks in advance
It depends on which version of #nuxtjs/firebase you are using, because this package #nuxtjs/firebase is not compatible with firebase version 9+ supporting tree-shaking.
So you need to downgrade you package to firebase version 8 and prior.
For more information, please check the authors github issues.
If you are using the new Modular SDK v9.0.1 you might get the above error as it does not use firebase. namespace now,
Try using getApps() instead of firebase.apps.
import { initializeApp, getApps } from "firebase/app"
import { getFirestore } from "firebase/firestore"
import { getAuth } from "firebase/auth"
const firebaseConfig = {...}
if (!getApps().length) {
//....
}
const app = initializeApp(firebaseConfig)
const db = getFirestore(app)
const auth = getAuth(app) export {db, auth}
I banged my head against this problem for a while - I was trying to use the Realtime Database in a dynamic page and getting the same error. I finally went back to this issue on the firebase module repo. Basically I had to do two things:
use the async asyncData method instead of just defining data properties; and
use both the app and params variables.
So instead of this:
export default {
data: () => ({
items: []
)},
async fetch ({ params }) {
const ref = this.$fire.database.ref(`foo/${params.slug}`)
const data = (await ref.once('value')).val()
this.items = data
}
}
I had to do this:
export default {
async asyncData ({ app, params }) {
const ref = app.$fire.database.ref(`foo/${params.slug}`)
const data = (await ref.once('value')).val()
return { items: data }
}
}
I found this line of code in the Firebase docs firebase.functions().useEmulator('localhost', 5001) that supposedly points your Vue app to the locally running emulator, but for some reason my project is ignoring said line of code and continues to call the remotely deployed function instead.
Here's what the relevant part of my #/plugins/firebase.js looks like:
import firebase from 'firebase/app';
import 'firebase/functions';
firebase.initializeApp({
apiKey: process.env.VUE_APP_FIREBASE_API_KEY,
authDomain: process.env.VUE_APP_FIREBASE_AUTH_DOMAIN,
databaseURL: process.env.VUE_APP_FIREBASE_DATABASE_URL,
projectId: process.env.VUE_APP_FIREBASE_PROJECT_ID,
storageBucket: process.env.VUE_APP_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.VUE_APP_FIREBASE_MESSAGE_SENDER_ID,
appId: process.env.VUE_APP_FIREBASE_APP_ID,
measurementId: process.env.VUE_APP_FIREBASE_MEASUREMENT_ID
});
firebase.functions().useEmulator('localhost', 5001);
const func = {
botcheck: firebase.app().functions('europe-west2').httpsCallable('validateRecaptcha'),
};
export { func };
And then to call the botcheck function, I'll run the following in a Vuex action:
const fb = require('#/plugins/firebase');
...
await fb.func.botcheck();
What am I doing wrong? How do I get it to correctly point to my locally running emulator?
Vue project versions:
vue: 2.6.11
firebase: 8.3.2
Functions project versions:
firebase-admin: 9.2.0
firebase-functions: 3.11.0
Let me know if I need to include additional information.
This line:
firebase.functions()
is functionally equivalent to:
firebase.app().functions('us-central1')
In your current code, you connect functions that don't specify a region to the emulator. Because you specify the region as europe-west2 when using it, you need to connect the europe-west2 functions to the emulator. You can do this by changing this line:
firebase.functions().useEmulator('localhost', 5001);
to use the correct region:
firebase.app().functions('europe-west2').useEmulator('localhost', 5001)
Additional Note: While firebase.functions() and firebase.app().functions() return the same instance of a Functions object (connected to the us-central1 region), firebase.app().functions('us-central1') (where you pass in the region) returns a different instance of Functions. You would need to connect each instance that you use to the emulator.
Here's your code as I make sure that useEmulator() is configured properly with Cloud Functions for Firebase Emulator. Feel free to try it:
import firebase from 'firebase/app';
import 'firebase/functions';
const firebaseConfig = {
// Your config
};
const app = firebase.initializeApp(firebaseConfig);
const functions = app.functions('europe-west2');
functions.useEmulator('localhost', 5001);
const func = {
botcheck: functions.httpsCallable('validateRecaptcha'),
};
export { func };
I have a class "Firebase" in which I write all the functions (that involve this service) used in my server side.
At first, the code was pretty clean and clear, as there were not a lot of functions in this class. Now, the class is super giant (more than 100 functions not very short). This is why I have decided to divide the class into modules (maybe not correct) to improve the clarity and organization of the project.
Something like this:
/* eslint-disable no-empty */
const functions = require("firebase-functions");
const admin = require("firebase-admin");
// Lazy initialization of the admin SDK
try {
const googleCloudServiceAccount = require("../../utils/json/googleCloudServiceAccount.json");
admin.initializeApp({
credential: admin.credential.cert(googleCloudServiceAccount),
databaseURL: URL,
storageBucket: BUCKET,
});
} catch (e) {}
class Firebase {
constructor() {
Object.assign(this, {
auth: admin.auth(),
firestore: admin.firestore(),
storage: admin.storage(),
});
}
}
Object.assign(Firebase.prototype, {
/* Methods */
});
module.exports = Firebase;
What I have in mind is to organize the code in modules like "firebase-auth.js", "firebase-storage.js", "firebase-firestore.js"... and finally, require all the modules methods (or something better, because it can be a really long list of imports) and assign them to my Firebase class prototype.
My question is: If in the methods of the modules I need to use Firebase.firestore, Firebase.auth... (which are members of the Firebase class), should I create an instance of the class in each module?
Any ideas?
Thank you.
try this, is not best but works as expectly
create a file with functions in export module like:
module.exports = function (app, dbConnection, firebase) {
exampleFirebaseFunction(){
}
}
and in your main file you call it as:
require('./routes/firebase-routes.js')(app, dbConnection, firebase);
Actually, the title is more or less the whole explanation of the problem.
I am trying to use Firebase inside my React app, which also uses NextJS and the problem is that I cannot get storage to work.
import firebase from 'firebase'
import uuid from 'uuid/v4'
// Init
try {
firebase.initializeApp({
apiKey: 'apiKey',
authDomain: 'authDomain',
databaseURL: 'dbUrl',
projectId: 'projID',
storageBucket: 'storageBucket',
messagingSenderId: 'id'
})
} catch (err) {
if (!/already exists/.test(err.message)) {
console.error('Firebase initialization error', err.stack)
}
}
console.log(firebase.app().name) // <- name
// References
const database = firebase.database()
const storage = firebase.storage().ref() // <- the problem
const documentImageStorage = storage.child('images/')
const documentsRef = database.ref('/documents/')
const documentsRequestsRef = database.ref('/requests/')
So, as I run the code I can confirm that the app works as the database works properly and the name ([default]) is returned correctly, but the line in which the storage reference is defined returns an error:
Firebase: firebase.storage() takes either no argument or a Firebase App instance. (app/invalid-app-argument).
Any ideas why this might happen? How can I solve it?
(Firebase Storage JS dev)
I was able to reproduce your error in Next.js. I'm not super familiar with it, but I understand Next.js does React-y server-side rendering, so the code you write for your page will generally be executed in the node server.
Unfortunately, Storage isn't supported in node right now, which includes server-side rendering contexts (feel free to leave a comment in the Github issue about your use case).
It should work in normal React apps (i.e. client-side code) though.
EDIT: found a (barely) workaround
The code appears to not crash in Next.js if you add an import at the top of the file:
import firebase from 'firebase'
import _s from 'firebase/storage'
import uuid from 'uuid/v4'
...
Regardless, the Storage library still isn't supported in node. Most anything interesting (uploading objects, getting object metadata) won't work, so unless all you wanted to do was call storage.toString() somewhere this probably doesn't solve your problem.
Firebase docs states that you save storage service reference to variable, then save storage reference to different variable.
https://firebase.google.com/docs/storage/web/create-reference
So I would do this like so:
const storage = firebase.storage();
const storageRef = storage.ref();
You can also put path to your storage folder to get reference for it, like this:
const documentImageStorage = storage.ref('/images/');
Ok, so I figured out how to make it work!
export const storage = process.browser ? firebase.storage().ref() : undefined
This way, storage part, which is unavailable on the backend isn't loaded, but on the frontend it is and everything works perfectly!
Thanks for both answers!