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.
Related
I'm currently using firebase for the backend of a project I'm working on. In this project, the client authenticates using the firebase-twitter sign in method. For the purpose of security, I'm trying to minimise the amount of communication between the client and backend when it comes to auth data. In jest of this, I'm wondering if there is a way to access the auth data i.e. the user's twitter key/secret (as well as things like the user's twitter handle) from the server-side after the user authenticates ? I figured there might be a way as the authentication happens through twitter + firebase, but I'm struggling to find the exact solution I need in the documentation (been stuck on this for a week now) so was hoping someone else already knows if this is possible and how :) cheers
Maybe not the best way, but you can try: on client side use RealTime database and add a new entry every time the user log in. They call this 'realtime triggers'.
You don't mention what front are you using, but on ionic is something like:
firebase.auth().onAuthStateChanged(function(user) {
if (user)
this.db.addLogin(user.uid)
});
On database class function:
addLogin(uid){
let path = "/logins/"
let ref = this.db.list(path)
let body = {uid: uid}
return ref.push(body)
}
On the server side, listen the path using child_added
var ref = db.ref("logins");
ref.on("child_added", function(snapshot, prevChildKey) {
var newPost = snapshot.val();
console.log("Uid: " + newPost.uid);
console.log("Previous Post ID: " + prevChildKey);
});
More information about triggers
I am trying to add socket.io functionality to my App.
I have never used socket.io before, so I have no idea how to progress from here.
I've used the MERN Stack until now, and the next step would be to implement socket.io for chat functionality. The problem is, I don't know when to connect, and how to preserve my sockets. The user can sign in, so I thought I could just connect after signing the user in, but then the socket is created in a component, and I can't access it from anywhere else.
The problem is, I use JWT tokens for authentication, so I have a function, that "signs the user in" when going to a new page, if the token hasn't expired yet.
if(localStorage.jwtToken){
const token = localStorage.jwtToken;
setAuthToken(token);
const user = jwt_decode(token);
store.dispatch(action_setCurrentUser(user));
store.dispatch(setGroupsOfUser({ id: user.id }));
const currentTime = Date.now() / 1000;
if(user.exp < currentTime){
store.dispatch(logoutUser());
window.location.href = './login';
}
}
I thought I could just connect in here, but then my ChatView component can't access it to send messages and stuff. I need a socket to send notifications, even if the user isn't in a chat room, and the ChatView component needs it to send messages.
Tried to connect after the login dispatch, and store the online users on the server, with their socketIDs.
If I try to search for a solution, every hit I get is about authentication using socket.io, but the authentication is already done for me so I'm not sure how to proceed.
As suggested, I decided to create the socket in my App.js and store it in my state.
I can use this stored state then in my subcomponents, and assign it on the server to a user after sign in.
You might want to look in redux. Since your having all the auth stuff and all . It might get messy handling app wide authentication .
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.
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.
I am creating a real time chat room. However unlike many other examples available, a single chatroom is only accessible to authenticated users, with whom the room is shared with using invitational based authentication.
Here are the steps:
1) Add User on invitation:
app.post('/shared', function(req, res){
Room.findOne({_id: req.body._id}. function(err,room){
new_shared = new Shared({room_id: req.body._id, user_id, req.body.user_id});
new_shared.save();
room.shared.push(new_shared);
room.save();
res.send(new_shared);
});
});
2) Once added to Shared, the user can realtime add messages to only the authenticated users in to room. How can we achieve that?
I am trying this:
app.post('/message', function(req,res,next){
// Save Message in Mongodb
//Emit a socket function - How do we initialize it within API?
}
3) On Client side, I am using Backbone JS. So once a new message is added, should I call .fetch() method on backbone to retrieve all messages.
I am relatively new to Socket.IO, so some of them might sound stupid.
From my point of view, mongodb and express have not great purpose in your chat application, socket.io instead carries all the burden.
first you should consider reconstructing the entire approach, you should try the following, for getting a clean result.
use a simple single route to login users such as /login so you can authenticate users using mongodb
to establish a connection to backend socket.io, try to serve the script which handles the connection
Since you use Backbone, try using a sync override, which uses socket.io instead of http requests Backbone.iobind
now finally the socket.io server which is attached to your expressjs server, should handle the rest
it should authenticate using expressjs sessions
it should handle connections and messages
there is a .room functionality built-in socket.io so there is no reason in using mongoDB and expressjs.
to sum it up, you could skip expressjs and make use of socket.io, http server, and mongodb if you store use credentials in database