I'm building a React app that will use Firebase Auth. I was storing my API key inside the .env file but I've read in the React docs that it is not safe, so I have created a custom and private route on my NodeJS server that is a simple GET request which sends me the data that I need(key, projectid, etc). And I think this is the safest way to do it, right?
Now I'm trying to get this data from my backend and I'm able to do it using console.log for example but I'm unable to integrate it with Firebase function. It keeps saying that it is undefined.
This is what I have tried so far:
import * as firebase from 'firebase/app';
import 'firebase/auth';
const API_URL = process.env.REACT_APP_API_URL;
let res;
export async function getFirebaseKeys() {
const response = await fetch(`${API_URL}/api/get`);
res = response.json();
}
getFirebaseKeys();
//I have also tried to use firebase.OnLog but no success.
//I have also tried to wrap firebase.initializeApp and my fetch in a function but it breaks
const app = firebase.initializeApp({
apiKey: res.apiKey,
authDomain: res.domain,
databaseURL: res.database,
projectId: res.projectId,
storageBucket: res.storageBucket,
messagingSenderId: res.senderId,
});
export default app;
This is my first project with Firebase and React so I'm sorry if i have missed something obvious.
What you're trying to do now is not really any safer than putting the Firebase config in the source (where it's intended to go). Hiding it behind a GET just makes people go through another step to get a hold of it if they want it.
There's nothing unsafe about putting the Firebase config in the hands of the public, as long as you are also signing in users with Firebase Authentication, and using security rules to determine who can access what data in the project.
Just do whatever is most convenient - security is not the issue here.
See also: Is it safe to expose Firebase apiKey to the public?
Related
Premise: I'm a newbie, so I'm aware that some of my questions or issues might sound obvious to the more experienced.
My situation is: I have an existing React frontend and Node backend. I need to add authentication to my app, so that I can provide login (and future registration) for my colleagues. We use AWS resources and there is an existing user pool in Cognito.
How do I go about doing this?
I've done some research and everything points me to AWS Amplify, but I've found the existing resources very confusing. Amplify seems to create a new, separate backend when I run amplify init, but I need to stick with the existing one.
Basically, all I need is the authentication piece, I don't want to use anything else from Amplify itself.
Apologies in advance if I might have missed something obvious. Thanks.
I have solved this exact situation with the Amplify library.
You can utilize the Authenticator component from #aws-amplify/ui-react where the docs are here.
In the most simple form, it would look like this:
import { Authenticator } from "#aws-amplify/ui-react";
import Amplify from "aws-amplify";
Amplify.configure(awsExports);
const App = () => {
return (
<Authenticator>
{/* Pass app entry as children */}
{({ signOut, user }) => <Home signOut={signOut} user={user} />}
</Authenticator>
);
};
//Object holding AWS auth config
//This is a seperate file which is imported
export const awsExports = {
Auth: {
mandatorySignIn: true,
region: "YOUR REGION",
userPoolId: "COGNITO_POOL_ID",
userPoolWebClientId: "COGNITO_CLIENT_ID",
},
};
So in my app, I only use this Authenticator component which does all of the interactions with the cognito pool and nothing else from Amplify. There's a number of different props you can pass into the Authenticator component so certainly review the docs.
I am currently using heroku to store my environmental variables for my firebase authentication initialisation. I am using my server to get the environmental variables and send it to the client using socket.io. Below is what I mean.
1) Example of sending environmental variable to client from server:
socket.emit('value', process.env.apiKey);
2) storing it as data[0] in the client:
socket.on('value', function(data) {
firebase.initializeApp({
apiKey: data[0],
});
})
Is this safe? Can someone from the client retrieve the value of the apiKey if I save it like this on the client?
Thanks
If the data is used from the client, it can be gotten from there by a malicious user. Looking up the data dynamically like you do here, merely adds an extra step.
But the data that you pass to initializeApp is basic configuration data that allows the code to find your Firebase project on the servers. It is not a secret, it's not a security mechanism ,and it can be safely shared with your users. See my answer here, for why you don't have to try and secure this data: Is it safe to expose Firebase apiKey to the public?
I'm working on a small website, serving static files using Firebase Hosting (FH) and rewriting all requests to a single function on Firebase Cloud Functions (FCF), where I'm using Koa (with koa-router) to handle the requests. However, when I try to parse the body of a POST request using koa-bodyparser, the service just hangs until it eventually times out.
The same thing occurs when using other body parsers, such as koa-body, and it seems to persist no matter where I put the parser, unless I put it after the router, in which case the problem goes away, though I still can't access the data, since it never gets a chance to be parsed(?).
The following is a stripped-down version of the code that's causing the problem:
import * as functions from 'firebase-functions'
import * as Koa from 'koa'
import * as KoaRouter from 'koa-router'
import * as KoaBodyParser from 'koa-bodyparser'
const app = new Koa()
const router = new KoaRouter()
app.use(KoaBodyParser())
router.post('/', (context) => {
// do some stuff with the data
})
app.use(router.routes())
export const serve = functions.https.onRequest(app.callback())
I'm still pretty new to all of these tools and I might be missing something completely obvious, but I can't seem to find the solution anywhere. If I'm not mistaken, FCF automatically parses requests, but Koa is unable to access that data unless it does the parsing itself, so I'd assume that something is going wrong between FCF's automatic parsing and the parser used by Koa.
I haven't been able to produce any actual errors or useful error messages, other than a Gateway Timeout (504), so I don't have much to go on and won't be able to provide you with much more than I already have.
How do I go about getting a hold of the data?
Firebase already parses the body.
https://firebase.google.com/docs/functions/http-events#read_values_from_the_request
It appears that the provided Koa body parsing middlewares don't know what to do with an "already parsed" body (ie an object vs an unparsed string), so the middleware ends up getting confused and does some sort of an infinite loop.
A solution is to use ctx.req.body because it's already parsed. :)
Koa rocks!
Question
I would like to store a Secret API Key for googleapis in a secure location. When I store the Secret API Key from googleapis as a Firebase Environmental Variable, the private_key is not processed the same as when I require("./privatekey.json"); See Issue below:
Context
I have downloaded and decoded a Secret API Key from Google. Most examples show saving the decoded JSON file within your project path and using require to pull the token into to code.
const SERVICE_ACCOUNT_KEY_FILE = require("./privatekey.json"); <----- This is Bad!!
const SERVICE_ACCOUNT_EMAIL = 'email#serviceaccount.com';
const jwt = new googleapis.auth.JWT(
SERVICE_ACCOUNT_EMAIL,
SERVICE_ACCOUNT_KEY_FILE.private_key,
null,
['https://www.googleapis.com/auth/analytics.readonly']);
I have used the firebase-cli to firebase functions:config:set Firebase Environmental Variables. When complete and redeployed, I run firebase functions:config:get and I see:
"googleapi_credentials": {
"private_key": "-----BEGIN PRIVATE KEY-----\\nMIIE ... q0DEg==\\n-----END PRIVATE KEY-----\\n",
Issue
When I configure googleapis.auth.JWT() I need to provide the googleapis Secret API Key. When I use require to pull in the Secret API Key, the requests work.
However, if I try to access the Firebase Environmental Variable to provide the Secret API Key, the requests fail.
var jwt = new googleapis.auth.JWT(
functions.config().googleapi_credentials.client_email,
functions.config().googleapi_credentials.private_key, <----- NOPE!
null,
['https://www.googleapis.com/auth/analytics.readonly']);
Debug
To see what's different I compared the console.log() of the two tokens in the firebase functions log view. The token I stored in the JSON file and in Firebase Environmental Variables looks the same in code, that is, both strings match and they include many \n (line breaks).
Now, when I review what the console.log() returns in the Firebase Functions Logs, I see different tokens.
console.log("JSON Private.Key", privatekey.private_key)
The view in the logs returns a formatted string with all \n replaced by line breaks, and the token is accepted.
console.log("Private.Key", functions.config().googleapi_credentials.private_key)
Logs returns a sting will all \n replaced by \\n., and the token is not accepted.
Final Note
The googleapis.auth.JWT() function can take an object for it arguments? Do I need to take this into consideration if using Firebase Environmental Variables?
Firebase environment details have a problem with add slashes and can break \n strings.
There is an open ticket on GitHub which should be referred to; github.com/firebase/firebase-tools/issues/371
Here is a hack I found posted by YunjorGlez. This worked for me.
You can use .replace(/\n/g, '\n') to remove the extra \ that is being added to the private_key.
const serviceAccount = functions.config().fireenv;
admin.initializeApp({
credential: admin.credential.cert({
"projectId": serviceAccount.project_id,
"private_key": serviceAccount.private_key.replace(/\\n/g, '\n'),
"clientEmail": serviceAccount.client_email
}),
databaseURL: whatever,
...
});
There is not much information on creating your own customToken on the Firebase Docs. It would be helpful for beginner developers to know how to create them to sort data to be accessible for specific users in an app (ios-swift), e.g. group1 has specific users who can view a specific section of the database.
In my case, I get lost in step 3 from 'Authenticate with Firebase'
When users sign into your app, send their sign-in credentials (for example, their username and password) to your authentication server. Your server checks the credentials and returns a custom token if they are valid.
from: https://firebase.google.com/docs/auth/ios/custom-auth
I am unsure about sending the sign-in credentials back to my server, where I will create a custom token and send it back. But how am I supposed to do this? Firebase Docs doesn't specify how.
I have set a server with node.js with the following code:
var firebase = require('firebase');
var admin = require('firebase-admin');
var FirebaseTokenGenerator = require("firebase-token-generator");
var path = require('path');
var servAcc = '/Users/myUserName/Desktop/nodeClient/service-account.json';
var tokenGenerator = new FirebaseTokenGenerator("firebase-secret");
var token = tokenGenerator.createToken({
uid: "clientId", groupId: "group1", managerId:"MG1"
});
firebase.initializeApp({
serviceAccount: path.resolve(__dirname, '/Users/myUserName/Desktop/nodeClient/service-account.json'),
databaseURL: "https://<app-name>.firebaseio.com/",
databaseAuthVariableOverride: {
uid: "clientId"
}
})
admin.initializeApp({
credential: admin.credential.cert(servAcc),
databaseURL: "https://apos-accentit.firebaseio.com/"
});
Just in admin.credential.cert(servAcc), I get an error in the terminal using nodemon.
Cannot read property of 'cert' undefined
How shall I proceed?
I solved this problem by updating firebase-admin. Apparently, the new admin.credential.cert() method was added on Nov 7th, 2016 with firebase-admin version 4.0.0. I was using a tutorial that referenced an older version of firebase-admin (3.0.0), and that version was what my package.json file referenced. Once I referenced the 4.0.0. I no longer experienced the error.
https://firebase.google.com/support/release-notes/admin/node
If you already have the json downloaded for your service credentials, create a 'js' file named credentials.js in the same folder of your main project. In this file create a variable called credentials and make it equal the json you have downloaded as your 'service-account.json'; also don't forget to export it at the end of your file as:
module.exports = credentials;
Save this file and at the top your main index.js file, import the newly created file named credentials.js as:
var serviceAccount = require('./credentials.js')
Now use this variable as your credentials. Inside of admin.initializedApp should look like this:
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://<NAME OF YOUR DATABASE>.firebaseio.com"
});
in short. The reason why node can't read your service-account.json is because the file extension is not readable by node. I had the same problem and this fixed it for me