FREELessons: 17Length: 2.7 hours

Next lesson playing in 5 seconds

Cancel
  • Overview
  • Transcript

3.2 Adding User Accounts

Pretty much all of the features that we have made in our contact application so far have used Angular in some way or another. However, in this screencast, we're going to add a feature that really has nothing to do with Angular but will round out our application and make it more complete,and that is User Authentication. Right now, our application is great for a single user, but if we want to allow multiple users to use our application, we need a way for our user to sign in and see only contact records that they have created, and not be able to see contacts that other people have created. So that's what we're going to do in this screencast. Now, there are some ways that we could implement authentication partially, using Angular. However, that's not what we're going to do in this series. We're just gonna implement a backend authentication only and not do anything special on the front end. So, to allow our users to log in we need a way to keep track of sessions on the server side. So that over multiple page refreshes, the user will stay logged in. So, to do this we need to install some session middleware for express. So, I'm going to do npm install express-session. All right. That was pretty straightforward. So, now, currently we have two servers on JavaScript files. We have the server.js file, which is pretty minimal, and we also have api.js, which is our rest api for interacting with our data.json file here which is the actual database. So we're going to create another server file and we're going to call this accounts.js. And in this accounts.js file, we're going to do something very similar to what we did in the api.js file where we'll basically create an express router that handles everything relating to, in this case, logging the user in and out [INAUDIBLE]. And then, we will reference this file in our server.js. So this to start, we need to import a bunch of things. We'll need to import express. So we'll go ahead and require express. We'll also need that body parser middleware that we have used previously. So we'll pull in body parser, we'll also need the session parser so we will pull in express-session. This is what we just installed. We'll need a database, so I will pull in the Bourne library. We will need the node crypto library, and this is what we'll use to encrypt our passwords. Excellent. So, now, let's go ahead and create our router by calling express.Router with a capital r. And let's also create our user database by calling new Bourne and we will call it users .json. All right. So, now, we can start with out router and we'll start by using the bodyParser middle-ware, bodyParser.urlencoded, which is one form of body that we'll need to parse, but we'll also wanna parse, we'll do bodyParser and we'll also parse json bodies. And both of these have to be in there for our authentication. Next, we want to use the session middle ware that we just installed and this session needs to take a secret. This secret is so that we can manage encrypted sessions and the secret is just basically suppose to be some random letters and numbers. Just like that, a random key, and now we're ready to create our log in and log our routes. Now, we have a couple of different routes that we're gonna need. When the user first comes to our website, if they are not logged in, we're gonna redirect them to the login page, and that will be a get request to login. Then, when they actually type in their username and password and press login, that will be a post to the very same route. Now, on that get login route, there's also going to be a sign up form, just in case the user is a first-time visitor to the site, and they want to create a new account, then when they press the create account button, instead of posting to login that will post to create. Finally, we'll send a get request to slash log out when the user is ready to log out. So these are the four routes that we need to create now. So we'll start with our get login route. This is actually the simplest one. We're just gonna do a response dot send file and we're gonna send public slash login.html. Obviously, we have not created this file yet, but we're gonna ahead and do that very soon. Next, we're going to have the post to log in. Now remember, this route will be called once the user has attempted to log in with a user name and password. So, this means we need to know what the username and password is, and so I'm gonna put them into this user object so we'll have the property username and this is gonna be request.body.username, and then we're also gonna have user password. But we can't just pass in this password like this. In our database, we're gonna store the passwords as hashed passwords not in plain text. However, this is gonna be plain text coming from the server. So we wanna wrap this in a call to hash function. Bounce back up to the top here, and we'll go ahead and quickly right that hash function, which we'll use to hash and password, all we have to do is return crypto.create hash, and this is the type of hash we're creating. There are a couple different ones you can use. I'm going to use shaw 256, for this example then we say, .update. And we pass the password in and then we say .digest and we choose what format we want it in. And we're gonna choose hex a decimal. And so this is the function we're using down here when we get our user object. So now we can do db.findone and we'll pass in that user. Then, we'll have a callback function that gets error and data. So now, we can say if data was returned, 'cause it's possible that no data would be returned, we'll set the request.session.userid equal to data.id, or the id of the user object that was found in the database. And then, we can do a response.redirect and we'll redirect back to the home page. Otherwise, we will do a response.redirect and we'll go back to the slash login page. Now, I could actually pull this redirect outside of our if else block here. Because if we redirect to the home route when the user's not logged in, they'll automatically be redirected once again to the slash login route. However, doing it this way not only prevents the second redirect but it does make the code a little bit clearer to show that we have to go back and try to login again if the login was not successful. Okay. And that is all we need to do to login a user. So now we're ready to go onto our create. And you know what? I'm gonna actually change this name to register which I think is a little bit clearer. So when the user posts their register user form, we're gonna get the username and password in the same way that we did when we post a login. So I'm gonna go ahead and copy that and paste it down here. Now,if you wanna store any other values about your user, for example, if our form allowed the user or to put in their full name or their email address. This is where we won't want to add these in. So for example, we might say email request.body.email. However, our form is only accepting a username and password so we can't get anything else from our form. But what I will add is an options hash. And this will be used in our next screen cast when we talk about allowing our user to choose certain options for our application. So now that we have a user object here, we need to make sure that no other users with this user name exist. So we'll say db dot find and we'll find where user name equals user dot username. Again, we'll have a callback function that takes error in data, and then we can say if not data dot length. So this means if data dot length basically is not zero. Then we know that we can create out user account, data.length is greater than zero, we know a user with this user name already exists. So, do response.redirect to /login once again. However, if the user does not exist we can go ahead and create it. So, we'll say db.insert, and we'll go ahead and insert that user object that we created above. Then inside of the call back, we want to do exactly what we did above here. And I'm gonna go ahead and copy these two lines where we set the session and then redirect. So if I paste these in here, you can see we are setting the request.session and I don;t think I mentioned above so I'll say it now. Request.session is an object that s created for us by this session middle where that we're adding right here. And any properties that we put on request.session will be available over multiple requests. So we'll say request.session userId equals data.id. And then, we will redirect to the home route. So we have one route left to add here, and that is the logout route. And this one is very simple, we'll just say request.session.userId = null; and then we will do res.redirect and we'll redirect back to the homepage. Now, even though this is all we need to do in the way of routes, I do wanna add one more piece of middleware here. And I want you to remember that every route or piece of middleware is checked on every single request that comes to our server. So that this piece of middleware that we're about to add here, once we add this rotor object into our main server dot JS file this piece of middleware will be checked against every single request that's coming in. Not just requests that get caught, but one of these log in functions here. And so here's what we want to do in this piece of middle-ware. If the req.session.userId has been set, meaning a user is currently logged in. Maybe they just got logged in now or maybe they were logged in on a previous request. Then, what we wanna do is set the req.user object as a shortcut way for us to access our user. I'll show you what I mean by this. So, we'll say if request dot session dot userId, then what we can do is, database dot findOne. And we'll go ahead and find one where the id is request dot session dot userId. And then in here, we'll just set request.user equal to the data which was returned, which will be the single user record. And then right at the end here, we will call next. So that the request is moved on. So what this piece of middleware basically does is, outside of this accounts.js, well, we could get request.session.userid. We won't have to because request.user will give us a shorter way not only to get the id but to get all the other information about the user. And if you've used other node back end authentication systems like, for example, passport. Passport must do something like this on the inside because it gives you access to a request.user object after a user has been logged in. And I guess I should have said earlier that we could have used something like Passport. However, I thought it would be fun just to write a very basic form of authentication on our own. So, now that we finished our authentication, we can do module.exports = router; and then let's open up our server. And up at the top here, let's go ahead and we'll request, we'll just call it users. And we can require accounts. And then right above our API here, we can go ahead and say,.use and we'll say users. We don't wanna add any specific prefixes to the URL so we just pass in users just like that. Now one last thing to do in this file while we're here. With the get request here where we send the public/main.html, which is basically our whole angular application. You don't want someone coming to our site to be able to load this unless they are logged in. So what we'll have to do here is say if request.user. Meaning, if a user is logged in, and notice how we're making use of that last piece of middleware that we wrote, then we will do a res.redirect and we will redirect them to /login. Otherwise, we can go ahead and send our public/main.html file. We've written all the JavaScript. But if we go back to accounts.js here, and go back to our get login page, you can see that we're sending a public/login.html file, which we have not created yet. So let's go ahead and open that up. Public, and we'll create login.html. This is actually gonna be similar to our main HTML page, so let me open that up so we can copy the similar parts. And I'm actually just gonna copy the whole thing and paste it all in here and then I'm gonna remove everything related to Angular. So we'll remove that directive there. We'll remove our navigation entirely. We'll remove the view directive. And we'll remove all of the JavaScript files and that should be all we need to do. Now, inside of where we originally put our views, we want to add our forms. And the two forms will be pretty much identical. So, we'll make the first one and then we can copy the second one. So, we'll say div class col-sm-6. So, we're gonna have each form taking up half the page. Then, we'll have an h2 and this one will say login. And then, we can have a div with a class of row and will also have a class of form group. Then we can put our form. And actually, no, you know what? Let's put our form outside of the h2. So, we'll say, form. I will give it the angular class of form-horizontal. And because this form will be submitted in the standard HTTP way, we'll have to give it an action which will be / login the route that we want to post it to and the method of course will be post. And so that form will wrap both of those elements that we've already made. Okay. So, now back inside of our row and form group. Let's add a label, and this label will have a class of form-control. It will also have a class of col-sm-2. And we'll just say username for the text of the label. And you know, why don't we also give our label the for attribute, just to be thorough. So we'll say, for username. And now underneath this, we can have an input element that will be type, text. We'll set its name equal to username. Give it a class of form-control, and we'll also say that it is required. Okay. So, that looks good for the first part of our form. Oh, no. Actually, I forgot. Since I gave the label a column width, I should give that to the input element as well. But I can't put it right on the input so we'll just wrap it in a div with a class of say, column small dash 8. All right. That looks good. So now, I'm gonna go ahead and copy this, this form group element right here. We'll just copy that and paste it right below. And now, we can change this up for the password, so we'll change the label to, for being for the password. We'll change the text here to Password, and the type will also be password, and the name will be password, as well. And it will stay required. All the other classes can stay the same. Excellent. So now, we have our user name input, our password input. So now all we need is our button. Okay. So let's do one more of our div with a class of both row and form group. And in here, we'll have a div with a class of column dash, small dash.offset-2 and col-sm-10. And this will just offset our button just a bit. And so in here, we'll add the btn and btn-primary and we'll just add the text Log In. And that is absolutely everything we need to do for this form. Now, I can go ahead and I copy right from where we have the column small six, so I can copy that whole thing. And then, I'll paste it underneath. And all we really need to change is in the h2 instead fo saying login, we will say sign up, and the form itself of course, instead of the action going to login, the action should go to register. We still want a user name and we still want a password, and then down here in the button, instead of saying Log In, we will say Sign Up as well. And that should give us everything we need. So we've got our two forms, and so now let's go ahead and make sure this works. Now, here I have the contacts page loaded up from before we created our user authentication. And if everything went as planned, when I refresh this page, we should see our login screen because the application will realize that we are not logged in and, therefore, do not have the right to view the contacts page. And remember that action is happening in our server js file when we get any route that is not either our API, our users, or something from our public directory, then we need to check to see if we have a user logged in. And if not, we'll redirect to login. So, if I go ahead and refresh the page, notice that the behavior I just described is not what happened. We're still viewing this page. Now, there's a reason for this. And it was a little hack that we did way back in one of the early videos when we created the API js. You'll remember that I said that inside of api.js, we need a way to get only the records that were created by the logged in user. And for this one, we did our get all contacts here, we found everything with the user ID of the request .user.id. However, at this point, we hadn't created authentication, so we threw in this little piece of Middleware up above here. We said, if there is no req.userId, meaning if no user is logged in, then, we'll go ahead and fake a user logging in. And this piece of Middleware is still in play when we get down here to our catch all route down here. So I'm gonna go ahead and remove that and now if we come back to this page and we refresh it, we're still getting the contacts page but we don't have any records showing up so we're part way there. I think the last problem is actually a typo on my part, if we go back to our catch all route here, I said if there is a request.user. Redirect to login. That's actually backwards. I should say if there is no request.user. So exclamation point request dot user, then we redirect to login. Okay. Let's refresh this one more time, and there you go. You can see we are correctly direct rected to /login. And everything looks great except for our weirdly styled labels, I bet I just applied the wrong class to those labels, let's see. I applied the class from control and these should all be the class control-label, so I'll go ahead and replace all of those and now you can see here is our log in page acting just fine. Now, we currently don't have any user created, so I'll go ahead and create user with my own name and let's see what happens. And there you go, we've been successfully logged into our application. Now currently, we're been redirected here to the route which doesn't actually show anything. If I click on all contacts, you can see, we, you can see all the contacts we've created. Now the reason you can see these contacts is because we've created these contacts with he user id of 1, which, of course, is going to be the id of the first user that was created. So we still have access to these contacts. So let's fix up two things here. For our front end API, let's make this go to slash contacts by default and let's also give ourselves a log out button here on the navigation. We can deal with the route by coming to our public slash source slash app.js file and let's add an otherwise block to our routeProvider call here, so we're gonna otherwise meaning if it's any route. That is not listed above, then we will redirect to, and we'll redirect to slash contacts, which is the first route that we have here at the top. So now if I come back and refresh the page here, notice we will have to log in again. And that's a side effect of using node demon on the backend, it refreshes the server every time we make a change to our application, and therefore, we need to sign in again. However, that won't happen in the actual use of our application, so now I'll click log in, we'll just be redirected to the home route on the back end and then our angular app redirected us to /contacts. Okay. Now for that log out button, we need to go to public views nav.HTML. And I'm gonna add another unordered list here, and this one will have the same two classes above, nav and navbar-nav. But then, we're also going to add navbar-right and this will push this over to the right side of the tool bar. And I'm going to add a list item and inside we're gonna have an anchor element and this is gonna go to slash log out. And we're gonna say Log Out. Now, the problem with having a link like this in an Angular application is that Angular is intercepting all of our different route changes like this. And so when we go to /log out. Our route provider here is going to look for a match, and the only match its going to find is otherwise, which will just redirect to slash contacts. But, what we want is for slash logout to actually be a request made to the server. And so, what we have to do to make this anchor tag actually request to the server and not be captured by angular, is give it a target, and we will set the target equal to underscore self. All right. So now if we come back and refresh our page once again, you can see now we have logout button over here. And if I click Logout, you can see we are logged out, and that just sends us back to the login page. And I can go ahead and login once again. And everything's working just fine. And so, there you go. We've just implemented a working user authentication system for our application.

Back to the top