Firebase Sending Request to Server from Client Safe Using Socket.IO? - node.js

I understand that security rules are the safest way to protect data however it doesn't support my method in storing user data when they click on specific elements. This is because if I turn write == true they are able to change the database willingly which I don't want them to do; I just want to record their input from my website privately.
Hence, my solution was to create another database on the server. Initialise the firebase realtime database on the server.js file. I thought this would be ideal, as the client doesn't access to any of the credentials (And yes I know, even if the user does have it, it's not that bad anyway but just in case).
So if I were to use socket.io to request from the client (all they see is "socket.emit('value', 'value')) then wouldn't it be safe as they are not seeing anything related to the firebase database as it is all on the server (which is not shown to the user)?
I just want clarification on if this is safe and ideal because it seems to logically work if I were to neglect the security rules.
Apologise to the previous users that replied on my previous post, this may be very similar but I have elaborated a little bit more to make what I am doing a bit more clearer.
Thanks for all your help.
Client Code:
var a = 0;
socket.emit('value', a);
Server Code:
firebase.initializeApp({
apiKey: VALUE,
authDomain: VALUE,
databaseURL: VALUE,
projectId: VALUE,
storageBucket: VALUE,
messagingSenderId: VALUE,
appId: VALUE,
measurementId: VALUE
});
socket.on('value', function(data) {
var ref = firebase.database().ref('node');
ref.set(data);
})

The code you shared indeed seems (unlike your first question) to no longer expose identity of your Firebase project to the caller. So there is no way for the caller to determine the database URL from what you shared.
Whether this is secure enough for your needs, only you can determine. But a few things to keep in mind:
If a user can find the database URL through another means, your ".read": true, ".write": true rules still allow them to both read all data, and write whatever they want.
Your code allows writing anything the user wants to the database. You'll want to lock that down either in your code, or with Firebase's server-side security rules.

Related

Retrieve all registered users in firebase

I am using firebase database in nodejs.
I simply want to fetch all the users that exists in my firebase database.
Currently I am trying this but it is only return one single user:
var config = {
apiKey: "AIzaSyBmgbfgdhs4efg9gFo",
authDomain: "my-test-app.firebaseapp.com",
databaseURL: "https://my-test-app-default-rtdb.firebaseio.com",
projectId: "my-test-app",
storageBucket: "my-die-app.appspot.com",
messagingSenderId: "534345345",
appId: "1:534345345:web:cceb16e6vvdd456",
measurementId: "G-YDZ8Y87GETR",
};
firebase.initializeApp(config);
var ref = firebase.database().ref("users");
ref.once("value", function (snapshot) {
snapshot.forEach((child) => {
console.log(child)
});
});
In firebase users I can only see this single user aswell, but infact I have more users there:
{
"XG0hYDjhekjdfJX5zLmfUFPQBifkmZA3": [
"XG0hYDjhekjdfJX5zLmfUFPQBifkmZA3",
"user1#gmail.com",
"835457568",
"Michael James ",
"user1#gmail.com"
]
}
And this is the only user I am getting in code aswell.
I am not the owner of this database, may be thats the reason?
Is there any specific way to retrieve all users list?
I believe the question answers itself
In firebase users I can only see this single user
If there is only only user node in the /users node as shown in your question
users {
"XG0hYDjhekjdfJX5zLmfUFPQBifkmZA3": [
"XG0hYDjhekjdfJX5zLmfUFPQBifkmZA3",
"user1#gmail.com",
"835457568",
"Michael James ",
"user1#gmail.com"
]
}
that means only one node exists - therefore only one node can be retrieved.
The confusion may be that while Firebase has users that can authenticate, those users are stored in the Firebase back-end server only. It doesn't necessarily mean other user data was written to the /users node.
In other words, if the app code does not specifically write data to /users, it won't exist.
Check your authentication code and see if it writes data to /users at some point - if not, there's your problem.
If that's the issue, which I suspect it is, you can't retrieve a list of Firebase users from the SDK directly, but you can using Firebase Admin coupled with Cloud Functions.
Here's an example: Get All Users
Alternatively (and my suggested path), you can also add code to the app so when users authenticate, if their users node doesn't exist within /users, create it (using the users uid returned from the Auth parameter as the node key)
What user are you logged in as? Is that the user you can see? What are your Firebase security rules like?
If you have reasonably secure rules setup, that would be the reason users can only see their own nodes.
See these docs: https://firebase.google.com/docs/database/security#section-authorization

Where to add Firebase in Node

I will be making a Web App in Firebase. Problem is, I am still unsure of how a few things will work.
Eventually I will need a server (which will be in Node) for sending emails and such. One of my biggest questions though is where Firebase will actually be needed. Let me elaborate some more!
I see that in the docs (here) you can add Firebase to your server by adding the following code in Node:
var firebase = require("firebase");
firebase.initializeApp({
serviceAccount: "path/to/serviceAccountCredentials.json",
databaseURL: "https://databaseName.firebaseio.com"
});
But you can also add Firebase directly to the browser with the following code:
<script src="https://www.gstatic.com/firebasejs/3.1.0/firebase.js"></script>
<script>
// Initialize Firebase
// TODO: Replace with your project's customized code snippet
var config = {
apiKey: "apiKey",
authDomain: "projectId.firebaseapp.com",
databaseURL: "https://databaseName.firebaseio.com",
storageBucket: "bucket.appspot.com",
};
firebase.initializeApp(config);
</script>
So my question is in what circumstances would I do either of the above? When would I add Firebase to the browser, and when would I add Firebase to the server? What uses do both provide?
For instance, could I access the Realtime Database from the server without connecting to Firebase? And if I add Firebase to the server, do I then have to add it again to the Browser? Please explain, thank you!
You already have most of the parts of the answer in your question.
Say that you want the users of your web app to be able to send email. As you say, you'll typically want to do that from your server, since you'd otherwise have to rely on the email client of your users.
But even when it's your node.js server that sends the email, it's the users of your web app that determine when and where to send the email. So the users needs a way to talk to your node.js script.
You can easily let the users talk directly to your node.js server. Set up some express.js endpoints and you're in business. But then you'd need to set up security on your node.js server, ensure that you can handle cases where your users are submitting more email requests than your node.js script can handle, etc. Lot of plumbing work that has nothing to do with sending an email.
Another way to handle this scenario is to let the web clients write "email requests" into the Firebase database. Simply include the Firebase client (with the snippet you have) and:
ref.child('outbox').push({
to: 'puf#stackoverflow.com',
subject: 'nice answer!',
body: '...'
})
Now your web client is done and the user can continue.
On the node.js server you include the Firebase client (with the second snippet you have) and connect to the same database, waiting for the email requests to come in:
ref.child('outbox').on('child_added', function(snapshot) {
var msg = snapshot.val();
sendEmailTo(msg.to, msg.subject, msg.body).then(function(error) {
// if the message was sent, delete it from the queue
if (!error) snapshot.ref.remove();
});
})
This approach is covered in our classic blog post on Firebase application architectures as pattern 2.

JWT Authorization Over Socket.io Connection

The fact that I haven't found an existing answer for this makes me think I'm asking the wrong question. Please feel free to (gently or otherwise) push me onto a better path if necessary.
We use a dedicated auth server, the purpose of which is to (1) given login credentials, return a JWT with a near-term exp or (2) given a JWT, according to a set of rules, issue a new JWT. A refresh, essentially.
That all works ace, until it's hacked. But for now, it's ace.
When it comes to socket.io connections to non-auth servers, however, we're shooting more than a bit from the hip. I wonder if somebody would be so kind as to evaluate this process. (I'm happy to post more code; you tell me if it's relevant).
1) initial socket.io connection results in a challenge:
this.socket.emit('authenticate'); // the challenge
this.authTimeout = setTimeout(() => {
this.socket.disconnect('unauthorized', errors);
}, TIME_TO_AUTHENTICATE); // the response kills this!
this.socket.on('authenticate', token => {
clearTimeout(this.authTimeout);
this._authenticate(token)
})
2) subsequent messages must contain a "payload" message in the form:
payload = {token: 'foo', message: 'bar'}, which token would be accepted if valid or returned if invalid.
In addition, the resource server sends its own periodic heartbeat, which must be acknowledged by heartbeat {token}.
My question, thus is: this seems too easy; am I cutting corners somewhere? Could you defeat this feeble fortification?
Just to be clear, we're looking to roll our own module here. I'm happy to look at anything existing; just haven't found anything I could begin to convince the bosses is fully baked for our needs.
Many thanks in advance.
I cannot fully analyse the method or ensure it doesn't have flaws, however I'd like to point out some things that came up to mind:
Apart from disconnecting the user in case of timeout on authentication challenge, you must ensure that the server does not send any non-public message to this user until after the authorization challenge is actually fulfilled successfully. Otherwise, there is a period until timeout where the user could receive a message without being authenticated.
I assume that you are also disconnecting the socket if token is invalid (or someway preventing non-public message to be sent).
This article is about authenticating socket.io communications using JWT. It is from 2014 so it might be a little bit out of date but I think that the core concept is still valid.
Associated with the article, there is a tool built specifically to authenticate socket.io connections using jwt. Even if you don't want to use it, you might want to explore its code looking for "inspiration". You can find it here: socketio-jwt.
You can see that this tool is able to use two different approaches:
An approach pretty similar to yours:
from socketio-jwt/blob/master/lib/index.js
if(options.required){
var auth_timeout = setTimeout(function () {
socket.disconnect('unauthorized');
}, options.timeout || 5000);
}
socket.on('authenticate', function (data) {
// ...
// Token validation
// Emit "authenticated" event if token is valid, the server can use
// this event as a point to send messages, once token is valid
});
A "One roundtrip" approach that basically uses query strings during handshake. And whose main drawback is that the token is exposed in the URL, so it might be logged, or getting exposed.

Is 'long-term credentials' authentication mechanism *required* for WebRTC to work with TURN servers?

I'm intending to run my own TURN service for a WebRTC app with coturn - https://code.google.com/p/coturn/. The manual says this about authentication and credentials:
...
-a, --lt-cred-mech
Use long-term credentials mechanism (this one you need for WebRTC usage). This option can be used with
either flat file user database or PostgreSQL DB or MySQL DB or MongoDB or Redis for user keys storage.
...
This client code example also suggests that credentials are required for TURN:
// use google's ice servers
var iceServers = [
{ url: 'stun:stun.l.google.com:19302' }
// { url: 'turn:192.158.29.39:3478?transport=udp',
// credential: 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
// username: '28224511:1379330808'
// },
// { url: 'turn:192.158.29.39:3478?transport=tcp',
// credential: 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
// username: '28224511:1379330808'
// }
];
Are they always required? (Coturn can be run without any auth mechanism, but it isn't clear from the man page whether it's strictly required for WebRTC to work)
If required, can I just create one set of credentials and use that for all clients? (The client code example is obviously just for demonstration, but it seems to suggest that you might hard-code the credentials into the clientside code. If this is not possible/recommendable, what would be the recommended way of passing out appropriate credentials to the clientside code?)
After testing it seems that passing credentials is required for clientside code to work (you get an error in the console otherwise).
Leaving the "no-auth" option enabled in Coturn (or leaving both lt-cred-mech and st-cred-mech commented) but still passing credentials in the application JS also doesn't work, as the TURN messages are somehow signed using the password credential. Maybe Coturn isn't expecting the clients to send authentication details if it's running in no-auth mode, so it doesn't know how to interpret the messages.
Solution
Turning on lt-cred-mech and hard-coding the username and password into both the Coturn config file, and the JS for the application, seems to work. There are commented out "static user" entries in the Coturn configuration file - use the plain password format as opposed to key format.
Coturn config (this is the entire config file I got it working with):
fingerprint
lt-cred-mech
#single static user details for long-term authentication:
user=username1:password1
#your domain here:
realm=mydomain.com
ICE server list from web app JS:
var iceServers = [
{
url: 'turn:123.234.123.23:3478', //your TURN server address here
credential: 'password1', //actual hardcoded value
username: 'username1' //actual hardcoded value
}
];
Obviously this offers no actual security for the TURN server, as the credentials are visible to anyone (so anyone can use up bandwidth and processor time using it as a relay).
In summary:
yes, long-term authentication is required for WebRTC to use TURN.
yes, it seems that you can just hard-code a single set of credentials for everyone to use -- coturn isn't bothered that two clients get allocations simultaneously with the same credentials.
one possible solution for proper security with minimal hassle would be a TURN REST API, which Coturn supports.

Using NodeJs with Firebase - Security

Due to the need to do some server side code - mainly sending emails I have decided to use Nodejs & Express for the server side element along with Firebase to hold the data - Partly from a learning experience.
My question is whats the best approach with regards to using the client side Firebase library and the Nodejs library when doing authentication using the Simple Email & Password API. If I do the authentication client side and then subsequently call a different route on the NodeJS side will the authentication for that user be carried across in the request. What would be the approach to test the user is authenticated within Node.
One approach I assume is to get the current users username & password from firebase and then post these to NodeJS and then use the firebase security API on the server to test.
Essentially the problem here is you need to securely convey to your NodeJS server who the client is authenticated as to Firebase. There are several ways you could go about this, but the easiest is probably to have all of your client<->NodeJS communication go through Firebase itself.
So instead of having the client hit a REST endpoint served by your NodeJS server, have the client write to a Firebase location that your NodeJS server is monitoring. Then you can use Firebase Security Rules to validate the data written by the client and your server can trust it.
For example, if you wanted to make it so users could send arbitrary emails through your app (with your NodeJS server taking care of actually sending the emails), you could have a /emails_to_send location with rules something like this:
{
"rules": {
"emails_to_send": {
"$id": {
".write": "!data.exists() && newData.child('from').val() == auth.email",
".validate": "newData.hasChildren(['from', 'to', 'subject', 'body'])"
}
}
}
}
Then in the client you can do:
ref.child('emails_to_send').push({
from: 'my_email#foo.com',
to: 'joe#example.com',
subject: 'hi',
body: 'Hey, how\'s it going?'
});
And in your NodeJS code you could call .auth() with your Firebase Secret (so you can read and write everything) and then do:
ref.child('emails_to_send').on('child_added', function(emailSnap) {
var email = emailSnap.val();
sendEmailHelper(email.from, email.to, email.subject, email.body);
// Remove it now that we've processed it.
emailSnap.ref().remove();
});
This is going to be the easiest as well as the most correct solution. For example, if the user logs out via Firebase, they'll no longer be able to write to Firebase so they'll no longer be able to make your NodeJS server send emails, which is most likely the behavior you'd want. It also means if your server is temporarily down, when you start it back up, it'll "catch up" sending emails and everything will continue to work.
The above seems like a roundabout way of doing things, I would use something like https://www.npmjs.com/package/connect-session-firebase and keep firebase as the model, handling all routes through express. Easier if your express server is rendering templates and not just behaving as a JSON API.
If you are using Firebase Authentication, the client side can import the Firebase Library (e.g. for javascript) and authenticate directly with the library itself
import firebase from 'firebase/app';
const result = await firebase.auth().signInWithEmailAndPassword(_email, _password);
After that, the client can to obtain the ID Token, this token will be informed on each request that will be made to the server (e.g. as header).
const sendingIdToken = await firebase.auth().currentUser.getIdToken();
On the Node.js server side, you can install the Firebase Admin SDK, to verify if the user is authenticated on the Node.js server, like:
// Let's suppose the client informed the token as header
const receivingIdToken = req.headers['auth-token'];
admin.auth().verifyIdToken(receivingIdToken, true)
.then((decodedIdToken) => { /* proceed to send emails, etc */}, (error) => {...});
The Firebase Admin SDK gives full permissions to the Database, so keep the credentials safe.
You should also configure the Security Rules on Firestore (or Firebase Realtime), so the client side can still perform specific operations directly to the database (e.g. listening for realtime changes on a collection), but you can also restrict all access if you want the client to only interact with the node.js server.
For more details, I developed an example of a node.js server that uses the Firestore Database and handles security and more.

Resources