Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Microblog 1
  • Overview
  • Transcript

3.5 Writing Login Routes

In the previous lesson, we created the login template, which is what our users will see when they go to either login to our application, or initially sign up for our application. So in this lesson, we are going to create the routes that will back up that user interface. So, let's go ahead and begin with the sign-up route because that is exactly where the user begins. So, here, in our login.js, we're gonna add this to our list of routes and middleware that we are complying from login. So, let's go ahead and we already have the get login, which is what gets that login page. So, let's do router.post. And what happens when the user posts to the sign-up page? So again, we take a function that has a request and response as its two attributes. And in here, when the user is trying to sign up, the first thing we wanna do is check to make sure that there is not currently another user with that username, right? If they've selected a username that someone else has chosen, they need to choose a different one. So let's go ahead and check our database and we can do this similarly to how we did it earlier. Up here in our local strategy, we used our collections.where function to find a record in our database, so we're gonna do the same thing here. So inside of our sign up here, let's start inside of our if block. And we're gonna to say if users, that's the name of our collection, dot where and we're gonna pass it username, and we can say request.body.username. Now the reason this will work is because we have the bodyParser where, right up here, parsing URL encoded bodies. Because here in the log in template, you can see we have our sign up form. And when we post to this /signup route, the body of the request will include whatever values we have in these inputs. So we have right here user name and that means that over here in our sign-up route, we're gonna have the req.body.username. So we can find all of the users that have that username. And so we can say the ware will return an object with items so we can say users.ware.items.length, and if that does equal 0, we know that we do not currently have a user with that user name. So we could go ahead and sign up. However, we're gonna have an else portion here, and if we do have a user with that name, well then let's just do a response.redirect and we'll redirect back to the login page so that the user can try again. Now this is a pretty simple server back-end here because I wanna focus mainly on the front-end. So we're not gonna include any kind of warning telling the user what the problem is, however in a more complete application that is what you should do. All right, so now let's move back to the top portion of our if statement here, this being if the user does not already exist. That means, we have a green light. We can go ahead and create this user. So let's create a user object here, and let's fill in some of the properties that we have in our form. So we know that the user has a full name, right? That is, the req.body.fullname. We wanna get their email, which is request.body.email. We want to get their username, which is request.body.username. And we want to get their passwordhash, right? And remember at the top of this file, we do have the hash function, so down here we can call it and say, hash, and pass it req.body.password. Of course, notice we're not doing anything to validate any of these values, which again would be something you would do in a more complete server side application. Now we wanna add one other property here. Eventually our users will be able to follow other users. So we're gonna have an array property called following. And this will be right now an empty array, but later when our user starts to follow other users, we will add those users' IDs to this following array. All right, and now we can call users which of course is our collection.insert and we could insert that user object. And this is actually gonna return the user ID, which we're gonna need in just a second. So let's capture that in a variable, because now we've created a user. However this does not log the user in by default and we could just redirect them back to the login page and have them then login after they sign up, however, that's a step we wanna avoid. When a user signs up, let's log them in automatically. So, since we're not logging them in through the traditional route, which we'll create in just a second here, instead we can use the log in method that Passport added to our request object. So we can say request.login and we have to pass it, the user object. So let's say users.get and we'll pass it the user ID to get that user from the database. And then we have a callback function here which will take an error as the parameter. All right, and let's see, what do we want to do inside this callback? Well if there was an error, then we should pass this error down the line to further routes, just in case it is relevant there. And the way we would do that is we actually need to pass a third parameter to our router callback function here. We have request and response, we would also pass next. If there is an error then let's just return here and we'll call next and we'll pass at that error. But otherwise, in most cases, there shouldn't be an error so we'll just do response.redirect and we will redirect back to the home page. And this home page is where we will be serving our web application from, so the user will be logged in and they'll be able to view the home page and they will be able to start using our application. So this is what happens when a user signs up for our application. However, what if the user already has an account and they're just returning and logging in? Well, if we look back at our login template, we can see that when they click that login button, our form here will be posted to the /login route. So let's create that route down here. The bottom of our file, we'll say rotor.post. And we're posting to /login. And this time instead of logging in our own request handling function here, we're going to use Passport's function. So we can say passport.authenticate. And what this will do is actually check the request body for the username and password, and it will use our strategy, which we created up here, to actually authenticate our user and see if we return a user object, or if we return false. Now we need to tell Passport which strategy to use because we could have used multiple options. So we pass the type of strategy as a string to authenticate here as the first parameter. So I'll say local and that will tell Passport to use our local strategy. Then we do want to pass in an object with a couple of options here as the second parameter. And these are our redirects. So I have a success redirect and a failure redirect. So if we were successful in logging in, meaning if our local strategy up here returned a user, well then what we want to do is redirect to the home page. If we had a failure, and that strategy returned false, instead we'll redirect to the login page. And so this is all we need to do for our login form to work, so currently we have the sign up route, which will work for our sign up form. We have our login route here, which will handle our login form, and so this means users can successfully sign in, or create a new account. However, what they can't do just yet is log out. So we should create that ability. So, let's have router.get and we will get the logout route. And this will just be a very normal route, and just as Passport gave our request object a login function, it also gives it a logout function. So right here I can say request.logout and it's that simple. And then I'll do response.redirect and we'll redirect to /login. If they are on any page of our application and they click a logout button which we will include in some navigation, they will be logged out and then we'll just send them back to the login page. Now, at this point, we have all the routes we need. However, there is a little bit of helper functionality that we should add to this file, which will be useful from other parts of our application. First of all, we want to make sure that certain parts of our application are only accessible if the user is logged in. Our homepage, which will display our actual application, is our primary example of that. However, many of the APIPAs should also not be accessible unless the user is logged in. So, we need a function that will allow us to check to see if the user is logged in. So why don't we make a helper function that will make it easy for us to check to see if the user is logged in, and if they're not then just forward them on to the login page. So, let's create a function and we will call this login required and this function will be used as a piece of express middleware or a route handling function. So it will take a request, a response, and a next function as its three parameters. And then we can just say if, request, and then we can say if this req.is is Authenticated. This is another one of the functions that passport adds to our request objects, an isAuthenticated function, and this will return true if the user is logged in. So if the user is logged in, then we can just call next and move on to our next piece of middleware without having to worry about anything, because we know that the user is logged in. However, if the user's not logged in then let's do a response.redirect and we will send them back to the login route slash login route. There is one other function related to user accounts that we're going to put into this and that is what I'm going to call makeUserSafe. Since this is a front-end web application, we're going to be sending user information to our client side. And we have these records as JSON in our local ADB database, and so it's very easy for us to just take that JSON and send it right back to the client. However there is some information in our user records in our database that we don't want to send to the client side, right? In our case, maybe that's just the password hash field, however in a bigger application, that would probably include other fields as well. And so we need a way to simply filter those out. So this function will take a user, and it will make a safe user. So let's create an object here called safe user, and that's an empty object and then let's create an array of safekeys, and this is just basically a white list of keys that we are going to allow the client to see. So that will include the user ID. That will include their full name. It will include their email address. It will include their user name, and it will include the list of people they are following. In our case, it might seem easier to use a blacklist to say what keys do we not wanna keep? In in our case, that is just password hash. However, I've done it this way, because this is more future proof. If we were to add other fields to our users in the future, some of those fields might be fields that we don't want to be made public. And if that was the case, we would have to remember to come back to this function and update our blacklist with the new fields that should not be included. However, if we only include the fields that we know we want to show our client, well, then, in the future we don't have to worry about adding fields and forgetting to blacklist them because any fields that we add will, by default, not be shown to anyone using our API. Instead, we would have to come to this function and explicitly allow those new fields. So let's do safekeys.4h and we will loop over each of these keys. And we will make the value of safeUser[key] equal to the value of user[key]. So we'll just copy those properties off the user and finally let's just return our safeuser. All right, now loginRequired and makeUsersafe are two functions that we will need on the other side of this file, so let's add them to our exports object here. We will say exports.required = the loginRequired function and exports.safe = makeUserSafe. Since we've added all these routes to the router, and then we already have our exports.routes here, those will already be available from inside server.js here. However, back here in server.js, we want to make use of the required and safe properties. We want our get all route here to be login required. So we can just do login.required. Now this may be a feature of Express that you are not familiar with. Basically, we can pass multiple functions to each one of these verb methods. So we can say in our get catch all route here first the request goes through the login.required function and then if that function calls next it will be passed on to the next function in this list. And so we could have a whole bunch of functions here that are all being called when we have a request that matches the parameters here. In this case, our catch all route for get requests. So, this way, when this route is being handled, if the user is not logged in, they'll be redirected to the login route. So if you think about what this means, by default, if you go to local host port 3000, we'll be redirected to slash login, which is exactly what we want. When the user is logged in, we want that user's information to be available in our template. So I'm gonna open up views slash index.ejs and right above our current script tag, let's create another script tag here. And we'll create a global variable here called user. And we will just use the EJS tags to interpolate a value. Now notice that I'm using less than sign, percent sign, hyphen instead of less than, percent, equals. This means that we will not escape the values. So all the quotes on all the properties of this object will not be converted into HTML entities, so that it will be seen as proper JSON. So let's call this the user. Now, if we're passing a user value into this template, that means we need to actually be passing it in here where we do the rendering. So, let's have an object here as our second primary to render, and let's get user, and we can actually get the currently logged in user by doing req.user. And note that because we have login.required here, we don't have to worry about request.user being undefined because this second route handling function here will not even be run if the user is not authenticated. However, this object here will include the user's password hash, so let's wrap this in login.safe and that way we will not see that password hash on the client side. All right, so with all of this in place, we should be able to successfully create our user account and be logged into our web application. Let me come to the terminal and start up our server, and it looks like we have an error typo exports. No problem. Let's come back to login and correctly spell exports there. And now if I restart the server, everything looks good. So now I can come to localhost port 3000, and notice that if I try to go to the root, we are redirected here to our login page. So let's create an account, right? I'll put in my name and my email address, I will choose a username and a password and now when I click sign up, all right. We have a little bit of an error here, so let's work through this. We have undefined is not a function in the hash function on login line 11. Okay, so let's see what's going on. So the problem here is that inside this hash function, something is undefined. Now, I know that it's none of these functions being undefined. And I know that it's not the crypto object because if it was any of those, we would have an error about some property not being a property of an undefined object. So to me this probably means that the password that's being passed in is nonexistent. This password attribute here is undefined. In fact, if we were to console.log password here, and then let's put out the password after that, we'll probably see that this password is undefined. We'll have to restart the node server and then let's go ahead and try to sign in again here. And if I sign up, and then let's flip back to the terminal here, notice that, what I thought was the problem is not the problem. Okay, so let's see. Oh, you know what it is? I think it's that create hash here, this h is supposed to be uppercase. So I guess my reasoning about the error message was wrong. Okay, so crypto.create hash with an uppercase h, that sounds correct. Let's go ahead and start our server up one more time. And now let's try to create our account one more time. And when I click Sign Up, notice that we're taken back to the root route. Now currently there's nothing displaying there, however that is the correct behavior. Let's pop open our JavaScript console and looks like we do have an error here, local host on line 10. If we look at our page here, okay, you can see that we do have a bit of a problem. Currently our object is being displayed as object object, which is like the two string version of object. That's not what we want. We want this to be displayed as JSON. Now, the problem here, I think, if we go back to our index view is that we should pass this object here to JSON.stringify. We can't just display a raw object like that, we have to convert it to JSON first. Now, if I come back, I've restarted the server. If we refresh the page, because I've restarted the server, we are logged out because the server does not keep that session information when we restart it. And this time, we don't want to sign up, instead we want to log in because we have already created an account. So if I type in my username and password and try to log in, and it looks like it refused my login which means our strategy is not allowing us to login. So let's go back and look at that one more time. If I look at our strategy here, it seems like everything should be working just fine. We're looking for a user name property and a password hash property, but we have hash with a capital H here. What did I do down below? If we go down here to our sign up, you can see that when we sign up, I did not capitalize the H. Now since this is already in our database as a lowercase h, maybe we should just change it up here in our strategy, but I really like it with the uppercase H, I think. Of course that's more readable. So, let's change it down here. And then, let's go ahead and open up, we have our data being stored in the .data directory. So, let's open up .data, and in there we have this user's file. Which is the JSON that is being read. And, you know what, let me first make sure that the server is off. And then in here, we have. Right here, here's passwordhash. So let me just make that uppercase. And I'll save that. And now we restart the server. Let's refresh our page here. And let's go ahead and log in as Andrew. And now you can see, we are logged in and we're not redirected back to Log In. That's excellent. So now, if I open up our terminal, first, no more error notice. Second of all, if I look at our script here, you can see that here is our user object. We have USER as a fully uppercase variable there, our global variable. See ID, full name, e-mail, username and following array. Excellent, so now we have the users information here in the client side and we can start to work with that. So that is all we need to do for logging in and logging out and creating user accounts. Actually, let's try log out. So if I go to /logout, you can see I'm redirected to /login. Excellent, which means I am logged out. And if I try to go back to the home route, I'm redirected back to this page. Excellent. So we have a successful user account system. This means that we can move on to the very last part of the server side, which is our Chirp's API.

Back to the top