7.1 Logging In and Out
We have the ability to create and manage users. Now we need to let them log in. We'll write the login and logout functionality in this lesson.
1.Introduction2 lessons, 05:56
2.Managing Posts3 lessons, 38:13
3.Tag Management2 lessons, 27:29
4.Storing Data3 lessons, 47:22
5.Testing Functionality2 lessons, 33:06
6.User Management3 lessons, 1:11:48
7.Security and User Authentication2 lessons, 34:17
8.Enhancing the User Experience2 lessons, 36:38
9.The Front-End2 lessons, 36:01
10.Conclusion1 lesson, 01:22
7.1 Logging In and Out
We need to secure the administration area of our application. And we can start doing that in a variety of places, but for me, the logical place to start is the log in form, because that is the entry point of our administration area. And plus, in order to make sure that everything works, we're going to have to log in, in order to test. So, let's start by creating a new controller. This is going to called the admin controller and it's going to serve as the landing page, if you want to call it that, and it will also have the methods for logging in and logging out. So let's create a new controller, let's call it admin controller, and let's go ahead and let's set the RouteArea attributes and admin. Now we don't have to use a route prefix here. In fact, we don't want to because this is going to serve as the root of the admin resource. So index is our root, we need a route attribute there, and we will pass an empty string. And this could have anything that we would want. If we had a dashboard, we could display that there. But for the sake of simplicity, our landing page is going to be rather bare. It's going to have a menu that will allow us to go to the posts or the tags or to the users. But other than that, we're not going to really list anything there. But then we need a method for logging in. Actually we need two methods. We need one for get requests and one for post requests. So let's start with the get request. So Httpget, and then we can add the Route attribute. This will be for login. We don't need a slash and then some other parameter. We simply want to login. And then public. We need this to be async and task, which we need the using statement for System.Threading.Tasks and ActionResult. And the method name is Login. We're not going to accept anything because this is handling a get request and let's just return the view because really, we don't need to do anything else here. All the login magic is going to happen on a post request, so let's write that method or at least let's start writing that method. So httppost, the route will be login, and we want the same signature except that we are gong to accept an object. And here we can use another view model. Now, we could reuse our user view model because we need a user name and we need a password, and really that is all that we need. But we're going to go ahead and create a new view model, one that is specific for logging in. So let's Except an object that is called LoginViewModel, and let's call that model. And there's really nothing else that we can do here until we have that view model. So let's go ahead and create it, and this is something that we will only need inside of the admin area. So it doesn't make sense to put this outside of the admin area. So inside of the view model's folder in our admin area, let's add a new class called loginviewmodel. Now we need the user name and we need the password. And we can also have a check box for the remember me, if that's something that we wanted to provide. So we need a string that is called UserName. We also need another string for the password and then we need a boolean value for the remember me, and lets just call it RememberMe. And we can go ahead and add some data annotations here. Now all of these except for RememberMe is going to be required so lets go ahead and add required both to the username property and the password. We can also set the display, so display and then for the username we will set that to simply username, and we really don't need a display attribute for password because that's rather self explanatory but we could add one for the bullion value. So, instead of required, let's change that to display. The name is going to be rememberme. It's basically the same thing except that we have a space between remember and me. That way it is usable from a user's perspective. And, I can't really think of anything else that we need to have here. So let's go back to our admin controller and we are going to use this model object in order to log in the user. Now in order to log in a user we need to retrieve the user from the database. So we need a user repository. The user service really won't work here because, if you'll remember, the user service is primarily working with the user view model objects. Here we need CMS user objects. So let's have a private read only IUserRepository. We need a using statement for our data name space, and let's call it _users. And then let's have two constructors. The first is going to accept the user repository as a parameter, so IUserRepository. Lets call this user repository. And then inside of here, we will set underscore users equal to user repository. But then lets have a default constructor, one that will pass in a new user repository. So admin controller once again and we will use this and then new UserRepository. And that is it for our constructors. So let's go back down to the Login method. Now we need to retrieve the user with the given username and the given password, and our user repository does not have a method for that. We have one for the user name, but we need one for the password as well. So, we're going to have to add a new method. So, let's write the code first. Because that's going to be easier, because we can just hit Ctrl dot and then add the method to the interface and then we just need to implement that in the class. So, let's create a variable called user. And this is going to be asynchronous so await and then users, and we have get user by name. Let's do, get login user async, and here we will pass in the user name, model.UserName, as well as the Password. And then before we do anything else, let's create this method. So control.enter to add that to the IUser Repository interface. And then we need to go to that file and modify the signature, because it returns a task of object and in this case we need a task of CMS user. And let's change these parameter names, the first is username, the second is password. And then we can copy this signature and use that whenever we implement it on our repository. So, let's go to the data name space, and let's open up that file. I'm just going to add this to the bottom of the file, well, before the dispose method, and public async Task of CmsUser. And all of this stuff. Now here we need to await and we will use our underscore manager. And we are going to use its find A sync. Let's see what else we have here. Let's do find A sync. Because I believe that is for the log-in information. Yes it has the user name and the password. So here we will specify the user name and the password. And we need to return that so once we do that we can go back to the admin controller and we can start using this user object. So the first we need to do is check to see if we have a user with the given username and password. If not, we can add an error to our ModelState. And we don't want to be very specific with this error message. In fact, we can't be because we don't really know if it's the username or the password that is incorrect. But in any system, we don't want to be specific. We want to be generic. We want to say that your credentials don't work, otherwise an attacker might get an idea as to what works and what doesn't. So the user with the supplied credentials does not exist, and that will be fine there. So if we do have a user, then we need to log them in. And we do that with an authentication manager. But in order to get to that authentication manager we need to add a new NuGet package. So let's go to references, and let's manage NuGet packages, and let's do a search for Owen, and the one that we want is right here at the top for me. It's Microsoft.owen.host.systemweb. So let's install and we need to accept this, otherwise we won't be able to do what we need to do. And my font looks big, and it's because it zoomed in, so let me zoom that out to 100%. There we go. And now we need to retrieve that authentication manager, so let's call this authManager and we use the HTTP context. There is an extension method called GetOwenContent. This is given to us by adding in that NuGet package, and then we have a property called authentication, and then we use this authentication manager to actually sign in the user. It has a method, called sign in. And we need to pass two things. First of all is a set of properties. For example, we have that remember me field in our view model. And we can use that to maintain a persistent state. So we can new AuthenticationProperties, and we need a using statement here for Microsoft.Owen.Security. And we want a property called IsPersistent. And here we can say model.RememberMe. So if RememberMe is checked, then this will be persistent. The second thing that we need to pass to the sign in method is a claims identity object, and we don't have that. In fact, we could only get that from our user manager. So we're going to need to add another method to our user repository. So before we call the sign in method, we need to create this claims identity object. So we can call this userIdentity, and we will await. We will use the users repository, and let's just call this CreateIdentityAsync. This mirrors the method name on the user manager. And then here we will pass in our CMS user. So, let's hit control dot to add this method to our repositories interface. Let's go to that file, and let's modify this, because we need a task of claims identity. And we need a using statement for this class, if I could spell it correctly. There we go. And let's just copy this, and use this as the signature for this new method. So inside of our repository, we will have public async, and then we will paste that in, and then we will simply return await _manager, and we will call it CreateIdentityAsync method. We pass in the user, but we also have to pass in the authentication type. And this is a string, but thankfully we don't have to actually know what that string is, because we are given a class called default authentication types. And there are many properties. The first one is application cookie and that's what we are going to use, but you can see that there are some other options there. So application cookie and that's really all that we need to do here. So let's add the semicolon to make that an actual statement. We also need a using statement for claims identity, and then we should be ready to go. So we can go back to the AdminController, and once we have this userIdentity object, we need to pass that as the second argument to the sign in method. And once we are signed in, we can do whatever we need to. So instead of returning to View, we can redirect to the Index action. So, simply Redirect to action and then we will pass in index. And, that's really all that we need to do for this login method. So, the next thing is the logout. And logging out is so much easier than logging in. So, let's add in the routes, and this will be simply log out and then public async and then task of action result. And I don't think we are actually going to do anything asynchronously but it's always good to have async there in case if we ever decide that we need to, and let's call this logout. Now here once again we need our authentication manager, so let's call it auth manager. And HttpContext.GetOwenContext and then Authentication, and just as this has a sign in method, it also has a sign out so we simply call sign out. And then we do whatever we want to do here. We would probably want to go back to the home of our application, so we can do another redirect to action. That would be index on the home controller, and there we go. Next, let's add the views for the index and login action methods. Let's start with index, so let's right click in Add a View. We don't really need anything here, so let's just click on Add. And eventually we would add something here, but let's just close this file. And then we need one for the log in action. So, once again, right click in Add View. We want to specify create, or edit, it really doesn't matter because either way, it's going to create a form for us. And then or the model class, we want our log in model view, and everything else can be Default, and we will click on Add. Now we do need to make some changes here. First of all, let's get rid of this h4 element, and really this horizontal line. Or we can leave the horizontal line. And next, we have the UserName. We need to change the EditorFor to PasswordFor, for our password. And then let's change the text of our button, instead of create, let's do log in. And really that should be fine. So let's go back to the admin controller and let's add some attributes here. Now everything on the admin controller should require a user to be logged in if they're going to go to the index action, then they should be logged in. If they are going to log out, they should be logged in. Really the only times that they shouldn't be logged in is if we are trying to log in. So here we can add the AllowAnonymous attribute for both of these login methods, and then we should be good to go as far as permissions and all of that stuff for our admin controller. But before we run this, let's go down to the start up file, because if you remember, whenever we set up identity a long time ago, we had this log in path and it was set to account slash log in. Well now that is admin slash log in. .So anytime we tried to access a resource that requires authorization if we are not logged in, it's going to take us to the login page. So let's run this. And first it's going to take us to the root of our application. And then we can specify admin, and admin is protected, because it's going to use the index action method. And it should take us to the login page, and it does. So let's log in as editor, and the password is password. And then let's just log in. And here we are at the index action method. So that is working great, so now let's just do log out and that should log us out. And it does, but it took us to the wrong place. It took us to admin/home. So we will need to address that. But for now, that's okay. I'm more concerned with getting everything else working as far as permissions are concerned.