- [Instructor] We are able to add quizzes and questions to quizzes, but no one has ownership over the quizzes. So anyone using the web app can add any question, or make any changes to existing questions or quizzes. We might want this kind of behavior for public-owned quizzes, but for private-owned quizzes, we probably only want the owner to be able to make changes. To do that, we will have to allow the user to register and log in so that they can authenticate themselves and so that the backend can authorize them to make the changes to their own quiz.
Let's begin by going in our backend project and creating a new controller. So I'll right-click on Controllers, hit Add. Then I'll select Controller. Then we'll select API Controller Empty. We won't be using the scaffolding item, as we did before, since this controller won't have the common get, post and put functions our other controllers had. We only want to be able to log in and register. So let's hit Add. And I'll call it Account Controller.
Next, let's create our register function. So I'll need an HttpPost decorator, since we'll be posting the register info. And I'll make it asynchronous and it will return a task IActionResult, and I'll call it Register. We'll need to take in an email and a password so that they can register with that. So to do that we'll need an object that has both those properties.
So a quick solution would be to go above this class and create a very simple class that only our controller will require and be dependent on. I'll call it Credentials. And we'll add a property type string called Email, and another type string called Password.
Now back in our register function, we'll get that FromBody, so I'll specify that. I've typed Credentials, and we'll call it credentials. Next we need to return something, since we specified a return type. Typically, once the register process is complete, and the user credentials are validated, the backend would then send a token. And so we need to return a token. Typically, authentication is usually done with tokens or cookies. Cookies is the more traditional method, and can be slightly more complex.
Tokens on the other hand is the more modern authentication and we'll be using that, specifically JSON web tokens, or JWTs for short. So in this case, we'll need to create and then return a JSON web token. So, let's create a new variable called jwt and set it to new JwtSecurityToken. And we'll need to add the using system identity model, tokens.jwt name space. Then let's return new JwtSecurityTokenHandler.WriteToken and then we'll pass in our jwt.
And then we'll wrap this in an Okay. Go ahead and save this and let's try it out. So I'll start the server. And then let's go over to Postman and we'll add /account to the end of our default URL and then we'll use a Post action. Let's go ahead and hit Send. And you can see we're getting back a token. And if you're wondering what the significance of this token is at this point, I'll quickly go over that.
Just like we get back a token in Postman, we would get one inside our app when we register. But of course we'd type in the email and password we want to register with. At that point identity framework Core will then create an account for us. If everything goes fine with identify framework, we will then generate that token and send it back to our front-end web app. Then our web app will be programmed to store that token in the browser, specifically the browser local storage.
So if we go over to the application tab in our Developer console, and click on Local Storage, we can see an example token that's being stored. So when we close our browser and reopen it, this token will still be there and will still be authenticated. Now the key is, when we send a secure request, such as adding a new question to a quiz that only we own, we'll pass this token and the authorization header of that request.
Our backend will then decode that authorization header, and inside it might be the user id and some other information. From that, the server can then double-check this is the user that should be making this request and they are validated and authorized. And that's a brief overview of how the process will work. So the next thing we need to do is start working with identity framework. So let's stop our server, and let's open up our startup.cs.
And inside our ConfigureServices on line 25, below line 35 where we added DbContext, let's add services.AddIdentity. And then also apply IdentityUser. And to get access to that, we'll use Microsoft.Asp.NetCore.Identity name space. Then as a second parameter, we'll specify IdentityRole.
Then we'll add .AddEntityFrameworkStores. And in here, we'll need to supply a new database context, just like we have our quiz database context, we'll need to create one for users. So let's go back to our SolutionExplorer and create that. So I'll right-click on the project and add a new class. I'll call it UserDbContext.
Then on line 8 I'll have this class derive from IdentityDbContext, then we'll add the Microsoft.Asp.NetCore.Identity.EntityFrameworkCore name space. And I'll specify IdentityUser. And I'll add the corresponding name space as we did before. One important note is that by specifying IdentityUser, we'll be using a class that comes with the identify framework that has all the default properties, such as email, password, whether they are verified or not, et cetera.
If we want any custom properties, we'd create our own Identity user class and derive from IdentityUser, but for now to keep it simple, we'll just use the default. Then let's create a constructor that takes in DbContextOptions and we'll need the EntitiyFrameworkCore name space, and we'll pass in our class as the type, and we'll take in options.
Then we'll pass those back to the base. Let's save that and then go back to startup. Inside here, we can specify our UserDbContext. And just like we have in the AddDbContext on line 36, for our quiz context, we'll need the same for our user context. So I'll copy that line and paste it below.
And I'll call it user. Now that the identity framework is configured, let's go ahead and update our AccountController register function. So we'll go to the top of our function, and we'll create a variable for user, and we'll set it to new IdentityUser, and we'll need the Asp.Core.Identity name space. And then let's initialize it with the credentials. So we'll set username to credentials.Email.
And Email to credentials.email. Since we don't be using a username and an email, we'll set email to both the username and the email property. Then let's create a variable called result, and using await, we'll try to create a new user. But in order to do that, we'll need to get access to the UserManager. So in our controller, let's next add a constructor.
And then we'll inject the UserManager. Specify identify user again. Then let's create a new property, in our class, that's readonly, type UserManager, with the IdentityUser type called userManager. Then let's set this.userManager to userManager.
So now we've injected userManager and we can continue creating the user in our register function. So we'll use await userManager.CreateAsync. We'll pass in the user, and then the password, from credentials.Password. Now if the result does not succeed, then we'll return a bad request, and maybe we'll supply our errors for now with result.Errors.
In a production environment you might want to supply your own set of errors. Now if the result did succeed, we'll use await and using a SingInManager, we'll sign them in. And since we don't have access to that, we'll need to inject it, as well. So let's go to our constructor on line 24 and add that as a second parameter. And then let's create a property to set it. So I'll create a readonly SignInManager called signInManager property, and then let's set it.
So I've injected the signInManager, created a signInManager property in our controller class, and then I've set it inside the constructor. Now that we have access to the signInManager, we'll continue on line 41 with await signInManager.SignInAsync. We'll supply the user as the first parameter and then we'll set isPersistent to false. And since we're not using cookies, but the more stateless tokens, we'll be setting isPersistent to false.
So let's save this and give it a try. So I'll start the server, then inside Postman, once the server loads, let's try hitting Send again. And it seems we have an error. So let's go to preview. As we can see, password should not be nul. So let's go back to our Body tab and modify it so that we have an email and password property. And I'll set both of those to one.
Let's hit Send. Once again, we're getting back an error. And now we're getting back more specific error details. So we're not matching the built-in password validation. We're getting several errors, password too short, requires non alphanumeric, requires lower and requires upper. Let's modify our password accordingly. So I'll set it to capital A then bc and then an exclamation mark. Let's hit Send.
And we're getting back our token, so we know the user was created. So we've successfully set up identity framework, registered a new user and sent back a token. Now that our backend has a register end-point, let's see how we can call it from our front-end.
- Creating and configuring the Angular project
- Creating forms with Angular
- Creating get and post routes in an ASP.NET Core controller
- Updating Angular service to post to API
- Persistence with Entity Framework
- Displaying and editing data in Angular with ASP.NET Core
- Navigating to different views in Angular
- Associations between entities with Angular and Entity Framework
- Setting up Identity Framework