Storing firebase config in .env files - node.js

I have a Sveltekit project where I am using firebase for authentication. I am storing the firebase.js file which contains the config parameters like apiKey and authDomain in the \src\lib folder. I am storing the apiKey value in the .env file which is present at the root of the project and using the process.env.API_KEY to load the value for the same. However when I do npm run build I get an error in the browser console - "process is not defined". I tried the below approaches but none of them seem to work -
Approach 1 -
import { initializeApp } from 'firebase/app'
import { getAuth } from 'firebase/auth'
import { config } from 'dotenv'
config()
const firebaseConfig = {
apiKey: process.env.API_KEY,
authDomain: process.env.AUTH_DOMAIN,
}
const app = initializeApp(firebaseConfig)
const auth = getAuth(app)
export default auth
Approach 2 -
Here I used the env-cmd library which I found via another Stackoverflow post. Below is the updated code
import { initializeApp } from 'firebase/app'
import { getAuth } from 'firebase/auth'
const firebaseConfig = {
apiKey: process.env['API_KEY'],
authDomain: process.env['AUTH_DOMAIN'],
}
const app = initializeApp(firebaseConfig)
const auth = getAuth(app)
export default auth
I also updated the scripts in package.json like below -
"scripts": {
"dev": "env-cmd vite dev",
"build": "env-cmd vite build",
"package": "svelte-kit package",
"preview": "vite preview",
"prepare": "svelte-kit sync"
}
Neither of the approaches seem to work and I still get the same error i.e. "process is not defined". Even some of the other repos I checked on Github had the apiKey hard coded in the config file, which obviously is a bad practice, even for hobby projects.
Any ideas on how I could incorporate the firebase apiKey via the .env file?

From the firebase docs
Unlike how API keys are typically used, API keys for Firebase services are not used to control access to backend resources; that can only be done with Firebase Security Rules (to control which users can access resources) and App Check (to control which apps can access resources).
Usually, you need to fastidiously guard API keys (for example, by using a vault service or setting the keys as environment variables); however, API keys for Firebase services are ok to include in code or checked-in config files.

Your .env file should have variables like this VITE_SECURE_API_KEY=API-KEY
and you can use them in your client-side app using import.meta.env.VITE_SECURE_API_KEY.
dotenv works on the server-side for sveltekit using node-adapter, also the firebase-admin package works only on the node environment.

Related

Secure API keys. Environment variables. Proper way to deploy securing keys

I have a project that I built with Angular. I want to host it but it makes calls to an API with an API KEY. From my understanding, there is no way to secure your keys from the client. I have been researching how to accomplish this, but I cannot find anything useful.
I want to deploy this small app to Heroku to learn how to do this. In my environment.ts file Should I replace the values with the variables in the .env file, build the application, then just add the key/value pairs to Heroku's Config Vars?
You can create two environment files. One for development and one for production:
environment.dev.ts:
export const environment = {
API_KEY: "XXX"
};
environment.prod.ts:
export const environment = {
API_KEY: process.env.API_KEY
};
Define these files in angular.cli.json:
"environments": {
"dev": "environment.dev.ts",
"prod": "environment.prod.ts"
}
Add --prod to postinstall script for Heroku deploys in package.json:
"postinstall": "ng build --aot --prod"
And finally, add Config Vars in Heroku dashboard or using heroku-cli:
heroku config:set API_KEY=PROD_XXX
Have you considered creating a backend for the API call? Your server could function as a middleman to authenticate your user and only make API calls your code intended to make. It is not clear what kind of API is used in your app but it can be dangerous to expose API keys not intended for public use (unlike Firebase API keys which are meant to be public).
Anyway, you should never store credentials hard-coded or commited to repository through configuration files. The basic steps are:
Create a .env file in your project root and add it to .gitignore. This is for local development.
Set production config vars in Heroku CLI or Dashboard.
Update environment.ts:
export const environment = {
...rest,
exampleApiKey: process.env.EXAMPLE_API_KEY,
};

Unable to authenticate against Firestore emulator

I'm using Firebase admin with Node + Express to update Firestore documents from Appengine. I have some tests that I want to have using the Firestore emulator. Here's the error I'm getting:
Error in post /requests Error: {"servicePath":"localhost","port":8080,
"clientConfig":{},"fallback":true,"sslCreds":{"callCredentials":{}},
"projectId":"test-project","firebaseVersion":"9.4.1","libName":"gccl",
"libVersion":"4.7.1 fire/9.4.1","ssl":false,
"customHeaders":{"Authorization":"Bearer owner"},"scopes":[
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/datastore"
]}
You need to pass auth instance to use gRPC-fallback client in browser. Use OAuth2Client from google-auth-library.
Before each test I'm calling:
var serviceAccount = require(process.env.FIREBASE_SA_CREDENTIAL);
firebaseAdmin.initializeApp({
credential: admin.credential.cert(serviceAccount),
projectId: 'test-project'
});
And the test is using a class that simply accesses Firestore like this:
this.db = firebaseAdmin.firestore();
...
I've got the following npm dependencies:
"#google-cloud/firestore": "^4.7.1",
"firebase": "^8.0.2",
"firebase-admin": "^9.4.1"
"firebase-tools": "^8.16.2"
I'm starting the emulators and running tests with:
firebase emulators:exec 'jest --verbose=false'
I can't see what's incorrect in the config - As far as I can see, the emulator should accept all auth. The error message suggests it's using some frontend library rather than a backend library, but the dependencies all appear to be correct.
OK, solved. There was an important line missing from jest config:
module.exports = {
testEnvironment: 'node'
}
testEnvironment: 'node'. This was triggering some behaviour in the Firebase libs that made them think the environment was a browser.

Cannot find module '../models/user' Node.js on Server

i can start my node app fine on my local machine but if i then pull the project from github on my server and start it with npm start i get this error:
Cannot find module '../models/user'.
import User = require('../models/user');
registrationController.ts is trying to access the models/user.
This is how i import in that file:
import User = require('../models/user');
This is my folder structure. I get the error in the highlighted file:
This is my npm start script:
"scripts": {
"start": "nodemon --watch '*.ts' --exec 'ts-node' app.ts"
}
This isn't the correct syntax to use your user model into the controllers, you should assign the required model directly to a variable (either a const or var) as follow:
const User = require('../models/user');
This link has great details regarding the two ways (import and require).
It looks like mixing the es6 syntax with older syntax
use this way if you are exporting that module as a default export
import User from '../models/user';
otherwise use
const User = require('../models/user')
make sure you are exporting the module like this
module.exports = <Your Module>
My filename was User.ts on bitbucket instead of user.ts
I pushed it to bitbucket before with user.ts but the filename hasn't changed there!!! So i had to remove the whole project from bitbucket

How Do I Build For A UAT Environment Using React?

According to the React docs you can have development, test and production envs.
The value of NODE_ENV is set automatically to development (when using npm start), test (when using npm test) or production (when using npm build). Thus, from the point of view of create-react-app, there are only three environments.
I need to change root rest api urls based on how I am deployed.
e.g.
development: baseURL = 'http://localhost:3004';
test: baseURL = 'http://localhost:8080';
uat: baseURL = 'http://uat.api.azure.com:8080';
production: baseURL = 'http://my.cool.api.com';
How do I configure a UAT environment for react if it only caters for dev, test and prod?
What would my javascript, package.json and build commands look like to switch these values automatically?
Like John Ruddell wrote in the comments, we should still use NODE_ENV=production in a staging environment to keep it as close as prod as possible. But that doesn't help with our problem here.
The reason why NODE_ENV can't be used reliably is that most Node modules use NODE_ENV to adjust and optimize with sane defaults, like Express, React, Next, etc. Next even completely changes its features depending on the commonly used values development, test and production.
So the solution is to create our own variable, and how to do that depends on the project we're working on.
Additional environments with Create React App (CRA)
The documentation says:
Note: You must create custom environment variables beginning with REACT_APP_. Any other variables except NODE_ENV will be ignored to avoid accidentally exposing a private key on the machine that could have the same name.
It was discussed in an issue where Ian Schmitz says:
Instead you can create your own variable like REACT_APP_SERVER_URL which can have default values in dev and prod through the .env file if you'd like, then simply set that environment variable when building your app for staging like REACT_APP_SERVER_URL=... npm run build.
A common package that I use is cross-env so that anyone can run our npm scripts on any platform.
"scripts": {
"build:uat": "cross-env REACT_APP_SERVER_URL='http://uat.api.azure.com:8080' npm run build"
Any other JS project
If we're not bound to CRA, or have ejected, we can easily configure any number of environment configurations we'd like in a similar fashion.
Personally, I like dotenv-extended which offers validation for required variables and default values.
Similarly, in the package.json file:
"scripts": {
"build:uat": "cross-env APP_ENV=UAT npm run build"
Then, in an entry point node script (one of the first script loaded, e.g. required in a babel config):
const dotEnv = require('dotenv-extended');
// Import environment values from a .env.* file
const envFile = dotEnv.load({
path: `.env.${process.env.APP_ENV || 'local'}`,
defaults: 'build/env/.env.defaults',
schema: 'build/env/.env.schema',
errorOnMissing: true,
silent: false,
});
Then, as an example, a babel configuration file could use these like this:
const env = require('./build/env');
module.exports = {
plugins: [
['transform-define', env],
],
};
Runtime configuration
John Ruddell also mentioned that one can detect at runtime the domain the app is running off of.
function getApiUrl() {
const { href } = window.location;
// UAT
if (href.indexOf('https://my-uat-env.example.com') !== -1) {
return 'http://uat.api.azure.com:8080';
}
// PROD
if (href.indexOf('https://example.com') !== -1) {
return 'http://my.cool.api.com';
}
// Defaults to local
return 'http://localhost:3004';
}
This is quick and simple, works without changing the build/CI/CD pipeline at all. Though it has some downsides:
All the configuration is "leaked" in the final build,
It won't benefit from dead-code removal at minification time when using something like babel-plugin-transform-define or Webpack's DefinePlugin resulting in a slightly bigger file size.
Won't be available at compile time.
Trickier if using Server-Side Rendering (though not impossible)
To have multiple environments in a React.js application you can use this plugin
env-cmd from NPM
And after that Create the three files as per your need.
For example if you want to setup dev, stag and prod environments you can write your commands like this.
"start:dev": "env-cmd -f dev.env npm start", // dev env
"build:beta": "env-cmd -f stag.env npm run build", // beta env
"build": "react-scripts build", // prod env using .env file

Set up "firebase javascript" to use with "ionic serve"

I want to use Firebase Javascript, in an Ionic 2 project so I can develop the Push Notification logic that I want to apply, and test it in a browser, by using the CLI "ionic serve".
I've followed the step as explain in the doc under node.js / ES2015:
I did a CLI "npm install --save firebase" to add it to my project.
And then at the top of one of my project service I did:
import [regular Ionic 2 and Angular 2 import stuffs];
import * as firebase from 'firebase';
#Injectable()
export class PushNotifService {
constructor(private platform:Platform){
console.log("PushNotifService starts");
console.info(this.platform);
console.info(firebase);
}
}
I ran into the problem described in that SO thread.
I've tried then a different approach, by importing the file "https://www.gstatic.com/firebasejs/3.6.10/firebase.js", then I added it to [my project]/src/assets/js/firebase.js.
In [my project]/src/index.html i added:
<body>
<ion-app></ion-app>
<script src="assets/js/firebase.js"></script>
</body>
And in my service:
import [regular Ionic 2 and Angular 2 import stuffs];
declare var firebase: any;
#Injectable()
export class PushNotifService {
constructor(private platform:Platform){
console.log("PushNotifService starts");
console.info(this.platform);
console.info(firebase);
}
}
It does not work, it seems that the <script src="assets/js/firebase.js"></script> [my project]/src/index.html is not taken into account (it is not there when looking at the DOM with the Chrome console).
Any idea how to import the "https://www.gstatic.com/firebasejs/3.6.10/firebase.js" file without usin "npm install"?
SOME UPDATES:
In the ionic 2 project there is [my project]/src/index.html and [my project]/www/index.html. I was editing [my project]/src/index.html and changes after "bundles update" while "ionic serve" runs in command prompt did not apply on [my project]/www/index.html.
Now I've updated [my project]/www/index.html with:
<body>
<ion-app></ion-app>
<script src="assets/js/firebase.js"></script>
//or <script src="https://www.gstatic.com/firebasejs/3.6.10/firebase.js"></script>)
</body>
And it does work with service:
import [regular Ionic 2 and Angular 2 import stuffs];
declare var firebase: any;
#Injectable()
export class PushNotifService {
constructor(private platform:Platform){
console.log("PushNotifService starts");
console.info(this.platform);
console.info(firebase);
}
}
Afterwards I had to apply what is explained here:
I created a "firebase-messaging-sw.js" empty file in "[my project]/www/".
And finally, as it is explained here, "[my project]/www/firebase-messaging-sw.js" must contain:
// Give the service worker access to Firebase Messaging.
// Note that you can only use Firebase Messaging here, other Firebase libraries
// are not available in the service worker.
importScripts('https://www.gstatic.com/firebasejs/3.5.2/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/3.5.2/firebase-messaging.js');
// Initialize the Firebase app in the service worker by passing in the
// messagingSenderId.
firebase.initializeApp({
'messagingSenderId': 'YOUR SENDER ID'
});
// Retrieve an instance of Firebase Messaging so that it can handle background
// messages.
const messaging = firebase.messaging();
NB: I've detailed the steps that had caused me troubles thru the implementation, beside that getToken() or onMessage() from
firebase.messaging.Messaging managed to work in my PushNotifService class once those other things around were in place. I didn't detail the server side (PHP script to send messages or Firebase project settings).

Resources