Create an HTTP post service to send login data to node backend.
- [Instructor] Let's start by creating the Angular service first. So let's go to our Auth services, and above register, let's add a login function, and it'll take in our loginData. Now inside it, let's call http.post. We'll pass in the BASE_URL, plus /login, and we'll pass along the loginData.
Next, let's subscribe to it, and we'll simply console.log the response. Now that we have our front-end service up, let's go ahead and implement the back-end service. So let's go back to Visual Studio, and we can stop the server, and inside our Auth controller, let's add a new login Post route, and we'll add that right above register. So I'll start off with a decorator, and our route will be login, and it'll return a JwtPacket, as register does.
Now since we're getting a custom object sent to us that contains only the email and the password, we can't really use the user model, since it would have several empty fields, and might confuse future developers. So we'll need to create a new class that has just email and password. Since we won't be storing the new class, and we won't be creating it as an entity in our database, there is no reason currently to create it as a separate file in our models directory, since only our Auth controller needs it for now. For simplicity's sake, I will create yet another class above our controller, as we did with JwtPacket.
I'll call it LoginData, and it will have two properties, the first being the Email, and the second being the Password. And now let's pass that in FromBody as a parameter.
Inside the function, we will need to search for an existing user with a matching email. If it is found, we will then try to match the passwords, and if the passwords match, we will then send the user an Auth token. It's as simple as that. In production, of course, we would be using a full database, and we would hash the password so that we can't see them as raw text. Let's start by searching for the email in our user's API context. So we'll create a variable called user that will save the user we found, and through context.Users, we'll search for a single user that matches the criteria.
So we'll use SingleOrDefault, in case we don't find a matching email or matching password, we don't want ASP.NET to throw an error. So we use a lambda function, and we'll check that the user.Email matches the loginData.Email. If that matches, we also need to match user.Password with loginData.Password.
And we just need to convert the single equals sign into a double, to have a conditional check. At this point, let's seed the users like we did with messages, so that we don't have to keep registering a new user each time we restart the server. I will keep it short, even if it fails email validation, in order to make testing speedy. So let's go to our Startup class, and let's go down to our Seed function. Inside here, let's add one user.
So I'll add the email, just a simple A, I'll add the first name, so that we get the proper greeting, and I'll add the password, and once again, it'll be just A, so that we can easily log in. Now let's close this, and go back to our Auth controller. Let's set a breakpoint at the end of our login function, that way we can test whether we found a valid user or not.
Now that our user is being seeded, we can go ahead and test login. Just for a quick test, let's return null, since this function does return a JwtPacket, and we can't build our server without returning something. Then let's add a breakpoint at the return. Now let's save this, and we'll also need to do a quick fix on the front-end before we try this out. So back inside Visual Studio Code, inside our login component, we just need to append this to loginData.
Now let's run our server, and give it a try. So I'll type in A for the email, and A for the password. And our breakpoint did trigger, so our route is working. Now let's check if a user was found. As you can see, a user was retrieved, as the email and password did match. Next we need to create the token, and send it off. We are already doing that in our register, so to avoid duplicate code, let's move all of that logic into its own function called CreateJwtPacket.
So let's stop the server, and at the end of our class, let's add that function. So it will return a JwtPacket, we'll call it CreateJwtPacket, and it will take in a user. Now let's move all of our generation code into it. So I'll take the first two lines, Line 45 and 46, and cut them and paste them into our CreateJwtPacket.
And now let's take the last line, Line 50, and move it over. And all the references seem to be fine with this function, since we are passing in a user, and it is getting the first name already from User. So now let's just update our register to use CreateJwtPacket, and we'll pass in the user, and then we'll return that. Let's copy this line for our login as well, and I'll just clean up these empty lines.
So let's replace our return null with return CreateJwtPacket, and let's give this a try. So let's try to log in and see what response we get. And there it is, we have our token, and we have our first name. Now what if there's a mismatch in either the password or the email? As you can see, we're getting an exception, because there was no user found inside our login, and so this is a null reference, and so we're not able to pull up the first name.
Let's stop that. So before we try to create our Jwt, we need to do a check to see if the user did successfully log in, and that we have a valid user. So if user equals null, they did not successfully log in, and here we need to return some sort of error. But since we're returning a JwtPacket, we can't return anything but a JwtPacket. So let's change the return type from JwtPacket to ActionResult.
With ActionResult, we can return common HTTP code such as NotFound, and we'll pass in a custom error message, email or password incorrect. And if we did find a valid user, and the Jwt was created successfully, we'll return an Ok(200), and wrap that around our CreateJwtPacket. Let's give that a try.
So let's clear that, and again I'll try to log in with an incorrect password. So let's go to our network, and if we look at the response, we see an email or password is incorrect. Now let's try it with the correct password. Let's go to our console, and we can see we are getting our token. Make sure to test your register functionality as well, to make sure no bugs were introduced by extracting out our JwtCreate functionality. The last thing we need to do is authenticate the user in our Angular app on a successful login.
So back inside Visual Studio Code, let's go to our Auth service, and inside our login, we'll make sure that the token and name gets stored inside localStorage. We are already doing that inside register, so let's put that logic inside its own function. And I'll call that authenticate, and we'll place it below our login, and it'll take in a response. So inside here, let's copy all of the code between Line 32 and Line 39 of register, and I'll cut it and paste it inside authenticate.
Now we can just call authenticate in its place, and we'll pass in the response. Now let's do the same with our login. I'll copy this.authenticate from Line 31, and replace the console.log on Line 24. Let's give that a try. So let's try logging in with a correct email and password. And there we go, we see our correct greeting, and we're redirected.
And let's triple-check our application to see that both keys are in. Let's go back to Visual Studio for a second, let's stop our server, and let's take a look at our CreateJwtPacket function. One thing you might have noticed is that our token has no payload. It has nothing inside it, it's just an empty token. Usually we would put a few claims inside, such as who issued the token, who the subject is, which is usually something to identify the user of the token, such as the ID or the email, and when the token expires.
There are many more claims that can be made as well if needed. The reason we currently have no claims is because I just wanted to demonstrate a very simple implementation of a token with registering and logging in. But if we wanted to authorize a resource to only a specific user, for example, such as saving changes to a username, we would at least need to provide the subject as a claim, so that we can identify the user, and who they are, on the back-end. This will make more sense when we implement this in the following chapter.
- Setting up the app infrastructure
- Creating and configuring the Angular 2 project
- Displaying data in Angular
- Refining the layout
- Creating the ASP.NET Core project
- Creating a controller with ASP.NET Core
- Creating a nav bar
- Registering users
- Authorizing resources
Skill Level Intermediate
Q: This course was updated on 10/18/2017. What changed?
A: The following topics were updated: setting up the app infrastructure, displaying data in Angular 2, getting data from ASP.NET Core, creating a controller with ASP.NET Core, and authorizing resources.