1.5 User Accounts Setup
We want to allow visitors to create accounts on our website, so that they can edit wiki pages. In this lesson, we'll create these accounts. The user records will be stored in Firebase, just like our wiki data.
1.Getting Started6 lessons, 27:04
2.Building the Application13 lessons, 1:29:43
3.Conclusion1 lesson, 00:44
1.5 User Accounts Setup
Now that we have a super basic server set up, the first thing that we're going to do is create user accounts. We're going to allow our users to log in and out via Ajax, but we still want the authentication to go through the server. So we're going to create a server module here that will talk to the Firebase, database, and allow us to actually authenticate our users. Now to do this, we're going to have to start by installing a couple of different packages. Of course, to keep our users logged in, we're going to need sessions, right? So we'll start by installing express-session@1, and don't forget to add the- - save flag once again. We're also going to want to install the Firebase package, and this is what will allow us to connect with the Firebase APIs and install version two of that package. Next, we're going to install body-parser@1 to parse our HTTP request bodies. And, finally, we're going to install cookie-parser so that we can parse the cookies that our session will be using. Install version one of this package, as well. With all of those packages installed, we can now create an accounts file. So I'm going to open up accounts.js in my editor, and let's go ahead and get started. What we're going to do in this file is create an express router with three different routes, sign up sign in and sign out. However, as you know, we're gonna be using Firebase as our data store, so let's start by actually creating a Firebase instance. So the first thing we're going to do is pull in the Firebase package, and we can do that by just requiring Firebase. Second we want to create an actual instance of our connection to our Firebase database. We'll call this firebase with a lowercase F, and we can do this by running new Frebase. But then we have to pass our Firebase URL as a parameter to that constructor function. If I come back to my Firebase Dashboard, you can see that we have this URL which is our Firebase data URL. If I click on that it's loaded up in the address bar, so I can copy it. And I'll come back here to our editor and paste that right in. So now we have a working Firebase database. Now, remember, Firebase stores data as a JSON structure. But what Firebase allows us to do is get references to child values within that data structure. So we can create a variable here at, users, and I can call firebase.child and I can pass it the user's key. This basically gives us a direct link to a user's key within the root of our Firebase database. And we can add a property or add multiple properties if this is a list or a subobject or something, directly to this child reference. Now, the next thing we want to do is actually create our express router object. We can do this by requiring express, and then calling .Router. This is a function that will return a new router, and we'll able to apply this routor to our full application later. And that's how we'll use these routes as part of our application. Before we get to our own custom routes, let's add a couple of pieces of middleware. We pass middleware to our router.use function. We'll start by requiring body-parser,and calling that module's JSON method. This way we'll be able to parse JSON bodies, which is how our username and password will be sent back to the server when we're trying to sign in or sign up. We'll want to include the cookie-parser as well, and don't forget to call this as a function because this module returns a function. And, finally, we will require express-session. This is a function as well, so remember to call that, but we'll also pass an object with a couple of options. We have to set resave to false, and save uninitialized to true. Those are just required parameters, but then we'll also include a secret which will be used to do our encrypting. This can be whatever random string you want to include. Next, we're going to create a post request for the route /api/signup. This is the route that our sign up requests will be posted to. In the usual express style, we'll include a request and response parameter here in this callback. And the first thing we want to do inside this function is get the username and password. Now, these will be stored in the request.body.username and request.body.password, and we'll just capture those as variables. Next, we want to check to make sure that both of those properties have actual values. So we'll say, if we have no username or no password, well, then we'll just return a JSON response. So we'll do this response.json. And we'll give this two properties. We'll say signed in is false, and we'll say, message is no username or password. So, that way, our front end code will know that the user was not signed in. However, if there was a username, then let's do users.child, and we'll pass that username in. Now remember, users is a reference to a subobject within our Firebase JSON structure. And when we pass username to the child method of users, we're creating a subchild, or fetching a subchild, really. If this does not exist we will create it. But if it does exist, we're getting a reference to the username key, or the key with whatever the text of the username is, Inside the user's object. Now, remember, what we're doing in this method is creating a user account if it doesn't exist. So, of course, the first thing we have to do is check to see if the user does exist. So we can access the values of any of these properties within Firebase by listening for events. Here, we're going to use the once method and we're going to listen for the value event. Now, this may seem like a kind of counterintuitive way of accessing data, but this is the way that Firebase does it. Basically, what we're doing here is we're assigning this callback to the value of event. And the way the value event works is this callback function will be called immediately upon the registering of this callback function. And then any time the value of our username reference changes, it will call this callback again. Now, that's the behavior that we would get if we used the on method, right, so that this event would be called every time the value occurs for this username. However, since we're using once, it will only happen once, which means it will only happen the first time which is whatever the value is right now. So we're only gonna call this callback function once. Firebase callback functions receive a snapshot as a value. And we can check to see whether this is a key that is actually existing in our database by saying, if snapshot dot exists, then we can call this method. Now if the snapshot exists, that means we already have a user with this user account name, so we're going to send back another error message. Once again, the response will be signed in as false, and the message can be username already in use. All right, now if we don't get returned at that point, that means that the username does not already exist in the database, so let's create a user object. We'll, of course, put the user name in as username, but then we also want to include a password hash. So we're going to have to create a hash function which will allow us to hash the password that was passed in. I'm gonna go ahead and create a simple hash function up at the top here. This function will take a password as its only parameter. And to do hashing in Node, we're going to have to include the crypto library, which is one of Node's native libraries. So up at the top, I'll say crypto = require crypto. And inside the hash function, I can return crypto.createhash, and we'll just create a sha512 hash right here. And we'll update the value of this hash by passing in the actual password. And then we can get the hashed value by calling the digest function and passing it the parameter, hex. So we get the hexadecimal digest of our password. So now we have our password actually being hashed, so we won't store that in the database as plain text. So down here, with our user object, we now have an object that has a username and password, hash. So let's do users.child(username), and we'll set its value to the actual user object. So now, within our user's object in our Firebase database, we will have a child property which has this user's value. Next, we can log our user in by setting req.session.user to that user object. And then we can finish by sending back our JSON response. We'll say that signed in is now equal to true, and we'll pass that user object as the user property. Now that was for signing users up, but we also want to allow users to sign in if they already have an account. So this will be for the route /api/signin. And we'll start in the very same way, by getting the username and password. And I'll just copy the first few lines of that function from above. So we'll get the username and password, and if no username or no password were passed in, we'll return a message. However, if we do have a username and password, then let's go ahead and get the reference in the database once again. So we'll say users.child(username), and once again we'll get the value by listening for the value event. For this we need to check not only that the snapshot exists, but also that we have the right password, right? So we'll say if snapshot exists and if snapshot.child, and we'll get the passwordHash child, right, because, remember, we're just storing this as JSON objects. So when we stored our user objects, it has a passwordHash as one of its child values. And so we can get those within the snapshot by saying snapshot.child('passwordHash'). Now that returns a reference, so we need to get the actual value within that reference by saying passwordHash.val, and we'll compare that to hashing the password that was just typed in. So if all of this works, then we know that the user is signed in. However, this is a little bit backwards from the pattern we've kind of been following. We've been using our if statements here as guard clauses, so that we continually return errors as necessary, and then we return our actual successful response at the very bottom. This if statement to set up to return our successful response. So let's change this to be if snapshot does not exist, or if the password value is not equal to the hash. Then what we can say is we will return res.json, and, once again, we'll say signedIn is false. And our message will be wrong username or password. Now, if we get past this guard clause then we know that this is the right user. So let's get the actual value of the user. And we can export the whole snapshot by saying snapshot.exportVal. We'll log the user in by saying req.session.user = user. And then we'll send back our res.json similarly to how we did above. So we can give signedIn equals true, and we'll set the user to be the actual user object. We have one more route that we need and this is /api/signup. This is actually gonna be very simple. All we need to do is delete req.session.user to log the user out. And then as a response, we'll send back signedIn equals false, with the message, 'You have been signed out'. So with those routes in place, we can export the module, module.exports = router. And now we have a completed module here, and we can actually use this within our server.js file. So within server.js, underneath our static folder, we can add another piece of middleware. And this will be require, and we'll require our accounts file. And it's that simple. Now we have our accounts. Now, while we're here in our server.js file, we should update the value of user being passed to our template. Right now, it's just null. So let's change that to be JSON.stringify req.session.user, or if that value doesn't exist, we'll just return null. So this way, here in our index template, if there is a user logged in, we will get their user object in the index template. This way, of course, if the user returns to our web application while our session cookies are still good, then they won't have to log in again. All right, so those are our user routes. With all of this in place, I think we can get started on our front end code.