We've been in looking at a number of recent videos at Web Api applications. And something I've been promising is that we're going to look at securing those with authentication
and authorization. Authentication is the process of verifying that a user is who
they claim to be and then authorization is, once we know who they are, deciding what
they're allowed to do, because different areas of our Web Api application may have
different levels of access. And now there are so many ways of doing this - so many
different variations. The one I'm going to focus on over these videos is using things
called JWTs - JSON Web Tokens. But even then, there's a lot of variation in how we can do those
things. So I'm going to start off in a number of videos - 'cause it'll take a little while to get it
working - going through the most basic way, and then we can see some of the variations. And
so what we're going to do in this first video, is see how we can get a hold of these JSON
Web Tokens that we can then use to access the site. Now, if we take a look at Visual Studio,
and if we just go for now for a new project, and go for a Web Api application, click on Next
there, click on Next there. And then you can see that we have this Authentication Type. And we've
got a couple of options in there, apart from None. We've got Microsoft Identity Platform and Windows.
And those will generate for you your application with some of the basic code required for those in
there. But I'm actually going to use a different approach, I'm going to use probably the simplest,
a thing called Individual Accounts. And that's where we actually store the user information -
with their password, their login - in the local database, just like you'd store any data for the
rest of the application. And I'm going to do this without doing code generation when I create the
application. I'm going to add it to an existing application that doesn't have any authorization
or authentication code in there. So that's what we'll do. So if we just close that down, and
then here, I've got an application - basically, what we've been looking at for quite a while. So
I've got this idea of BookReviews. So there we've got a BookReview, we've got a ReviewContext
- Entity Framework. We're connecting to a SQL Server database. I've already populated
that database with some data. And then we've got a BookReviewController that gives us
access to that. And so when we run that up, we just got the Swagger interface, and we can use
that to test the various methods. And there we've got some books coming out. And if we just take a
quick look at the database for that, then we can see in here we've got this AuthenticatedLibrary
and in there, if we look at the data, we can see we've just got the one table, and there were the
BookReviews that we were looking at. So let's actually turn this into a secured application. And
the very first step I'm going to take is simply on that BookReviewController, I'm going to put
an attribute ‘[Authorize]’ … and just get hold of the namespace. And so that now means that this
controller, none of the methods in the controller can be accessed unless the user is authorised.
Unless they've been logged in. So we're going to look at some variations on that in a later
video. For now, it's just all or nothing - they can either use it or they can't. And as soon as I
put that in, we're going to see that when we now run it up with Swagger, or with anything else,
that now when I try to access one of these, what I get back is, in this case, a 500. We've
not actually activated authentication at all, so we're not allowed in there. But it's
impossible to get in there, because we've got no authorization mechanism. So what we need to do is
start introducing that. And so, as I say, what we're going to do is we're going to introduce this using
our own database. So I'll pop in another folder in here and I'll call this ‘Authentication’. And then
the first thing I'm going to do is put in here a class to represent the actual user. So we'll
have a class and we'll call this ‘LibraryUser’. And then in there, I'm going to derive that
from a base class called ‘IdentityUser’. And we'll get that and what you'll see
there - if we just take a look at that, and then look at that one - we can see that that
has lots of properties, like it's got an Id, a Username and EmailAddress, some other things like
that. So the basic information we're going to have for our user. And when we derive from that, what
we're doing is adding any additional information that is specific to our application, which you
may not have any at all, but in this case, just to have something, let's pop in there a ‘bool’ which
we're going to say is ‘RatingsAllowed’. So is this user allowed to add new ratings to the system?
And then also let's have another property, which is going to be a collection of those
BookReviews. And we'll just call that ‘Reviews’. So that's going to be the set of reviews that
this particular user has created. And because that's a reference type, it's given us a warning,
so I'll just put ‘required’ on there. And as we saw in an earlier video, that's a way of avoiding
getting the warning about nullable references when you're in a class like this. So that's our
user. And the next thing we're going to have to do is we're going to have to now take that user
and include it in our DbContext. So you will sometimes see a completely separate DbContext done
for the authentication compared with the regular data in the application, but I'm going to put them
together. And so I need to do a couple of things. One is this ReviewContext, I need to change the
base class so that it's no longer a DbContext, it's a thing called an IdentityDbContext. And
then we've got to install a new package for that. And we want to go for that first one there, the
ASP.NET Core, Entity Framework Core identity. So we'll install that and that's all happy. And
then also, we need to add another DbSet to deal with our LibraryUser that we just created. So
we'll say just ‘LibraryUser’ and we'll call that ‘LibraryUsers’. And then just
get hold of the namespace. And so that's our DbContext. And we're now in
a position where we can actually add that to the database. So the usual way of doing this,
we just need in here to do a ‘add-migration’ and we'll call that ‘AddUsers’. So you can
see we've already got the migration that I did to create the initial database with
the reviews. But now we can do this one. And then we'll do an update database and that will generate the new tables in the
database as we can see. And then once it's done that, if we go back and take another look and
just refresh that, we can see that it's given us a whole load of new tables that are related to
this, I'm not going to go through all of these, but the key one to look at is the AspNetUsers. So
if we take a look at the structure for that one, we can see that it's given us a lot of those
standard columns that we had from the base class, that's where we're getting Discriminator and
Email, NormalizedEmail. But you can see it's also got that RatingsAllowed, which remember was the
extra property that I put in there. And also, if we now take another look at the BookReviews table
and look at the designer for that, it's got a nullable LibraryUserId. So remember, I put in the
LibraryUser, it had a collection of BookReviews, therefore the BookReview has that in and it will
set up the foreign key that hooks those together. So the tables it's generated are integrated into
the existing tables. And you could do that in all sorts of ways - we'll make some use of that
in later videos. So that's us generated the database. Now what we have to do is allow users
to register with all of that. So that's what we're going to do next. And first thing we need to do
is a little bit in the Program file. So if we just go to Program, and then in here, I'm going to put
just after that - doesn't really matter where, but there we've created the DbContext. So I'm going to
say ‘builder.Services’ and then we're going to say ‘.AddIdentity’. And then that identity is going to
be generic, it's going to take the ‘LibraryUser’ that we generated, that we've got there. And
then also a thing called an ‘IdentityRole’, which we've got there. And then I'm going to add
into that one option. So you can do all sorts of options here, but the one that I want to do is
simply the problem that by default, you're not allowed to have spaces in user names. So what I'm
going to do is just change that. I'm going to say ‘options’ and then ‘.User’. And then there's lots
of things we can have in here for configuring the rules of what can go in here, but I'm going to
go for this ‘AllowedUsernameCharacters’, and I'm just going to say ‘+= “ “’. So now we're going
to be allowed to have spaces in the username. And then also going to chain onto the end of that,
I'm going to say ‘.AddEntityFrameworkStores’ and that one's going to be using our ReviewContext.
And then also going to add on to the end of that ‘AddDefaultTokenProviders’. And all of that
will just configure our middleware so that we can use this identity in the system. Now
I need to add a couple of new data types. So back in Authentication, I'm going to add a
class that I will call ‘RegistrationModel’ and so this is going to contain the data that we
need for a new user to register. And so we'll have a ‘prop string’, and this one, I'm actually
going to call ‘Username’ rather than ‘Name’. Again, make that ‘required’. And then I'm going
to have another string called ‘Password’. And then we'll also have the ‘Email’ address. And
on that, I'm going to give that an attribute of simply ‘[EmailAddress]’. And all that means is
that Swagger and tools like it can know that this is supposed to be an email address. While we're
doing that, let's add another model, which we'll be using a little bit later on. But this one is
going to be called our ‘LoginModel’. So we've got one that we use to hand over the data when we are
registering, but we're going to have another one who's to hand over the data when we're logging
in. And that one is going to be similar but different. That's just going to have the Username
and Password; we don't need the email address to log on every time, we just need that to register.
So we'll just pop those two in there, then we're going to need to add another controller. So we're
going to add one, and we're going to call this ‘AuthenticationController’. And then
the first thing we're going to do is put in this idea of registration. So to do
this, we need to have a ‘private readonly’ a thing called ‘UserManager’, and that is generic
for the user type that we just created, so our LibraryUser. And we'll call that ‘_userManager’.
And then we'll pop in a constructor that will have that injected into it. So we can just generate
that constructor like that, then we'll have our register. So this is going to be a POST method, so
we'll do ‘HttpPost’ and then because we're going to have more than one POST method - actually,
the login is also going to be one - we need to give it the name just to distinguish it. So
that's ‘Register’. And then that's going to be ‘public async Task<IActionResult>’, so nothing
unusual there. And we'll call it ‘Register’. And it's going to take from the body, this
RegistrationModel that I just created, and we'll call that ‘model’. And then what we're going
to do is firstly check to see whether there's already a user with that username, which won't
be very good. So we'll say ‘var existingUser =’ and then ‘await’. And then that's where this
_userManager comes in. Because we can say ‘FindByNameAsync’ and then we'll simply pass in
the ‘model.Username’. And so that will check for that. And then we can simply do ‘if (existingUser
!= null), so if there is an existing user, and then we can have a ‘return’. And the best error
for this is ‘Conflict’. And then just a message ‘User already exists’. Want to give back the
minimum amount of information because a hacker could start making use if you told them any more
than that. But you've at least got to tell them the user exists so they know they've got to
go for a different name. And because we've returned Conflict, we also need to be putting
on the ‘ProducesResponseType’ that we looked at in earlier videos. And that's going to be
‘StatusCodes.’ and then if we find Conflict in there, that's going to be ‘409Conflict’. So we've bailed
out if there's already a user, but now we need to create the new user. So we're going to say ‘var
newUser = new LibraryUser’. And then in there, we're going to say simply ‘Reviews =’ and then
we'll just have an empty ‘List<BookReview>’. And then the ‘Username’ is going to be
‘model.Username’. And then the ‘Email’ is going to be ‘model.Email’. And then something we
also put in there - and this is something from the base class - we put in a ‘SecurityStamp’ that
basically just means that you've got something unique for each one of these, so we do this
with a GUID. So ‘Guid.NewGuid’ and then turn that into a string. Okay, so that's our new
user created, then we need to actually add that in there. So we can say ‘var result =
await’ again on the ‘_userManager’ and then ‘CreateAsync(newUser’ and then also we pass in
separately ‘model.Password’ in there. And that will create the user with that password. And then
finally, we can just say ‘if (result.Succeeded)’ then we can simply return an
‘Ok(“User successfully created”)’. ‘else’ we're going to return a status code
of ‘InternalServerError500’ and then bit of explanation on that. So we can say something
like ‘Failed to create user.’ and then we can put all that together with a ‘string.Join’.
And then we'll just do a space separation of from that ‘result.Errors.Select’ and then
‘e => e.Description’ and then on the end of that a semicolon. So that may again be giving out
too much information, but for now, when we're developing, I think that's worth having. And
so we just need just to be complete on that, we need to have the status of ‘200OK’ and then
we also need the status of ‘500InternalServerError’. So those are the possibilities. So now we've got
the ability to register. So let's run that up, and you can see we've got that new Authentication
controller with the Register method. And we can see there - because remember, I put that email
attribute on the email property - then we can see it's showing, as an example, the correct format.
So let's try that out. So I'll go for Username, ‘Jasper Kent’ - so remember I allowed spaces when
I was setting it up in the Program. Password, let's just go for ‘Pa$$w0rd’ - so a standard
test password. Leave the email the same. And if I execute that, then we're getting back ‘User
successfully created’. And if we take a look back in Visual Studio and look at the database
again, and if we look at this AspNetUsers, then we can see we've now got that one in there.
And you can see the information that I've put in there. And also just worth pointing out is the
password is encrypted. This uses a technique of hashing - sometimes referred to as one-way
encryption - and that basically means that even if somebody stole this database, they could not
look up their password information. I may do a video later on explaining exactly how that works.
But it is a very secure method that we have in there. So we've registered. Now having registered, we need
to log in. So that's something else we're going to have to add to the authentication controller. And
another class I'm going to put in here, actually, I'm going to add a new class, and this one I'm
going to call ‘LoginResponse’. And on there, I'm going to have two things, I'm going to have
a ‘string’ and that string is going to be this JWT - this JSON Web Token that I've been
talking about is the key to this whole thing. And because it's a string, we'll make it ‘required’.
And then I'm also going to have another property, which is going to be a ‘DateTime’ which is going
to be an ‘Expiration’ date, because as we'll see, the thing about a JWT is we set it up with
an expiration that typically could be a few hours ahead - whatever you like, depending on your
security issues. But we can create the token and that will then be a valid login without any
further reference to the database until you go past that expiration. And we’ll see how that
works over this video and later videos as well. So that's what we're going to return from our login.
And so if we go back to our AuthenticationController, and so now we're going to have another POST
and this one, we're going to call ‘Login’ and so we'll have a ‘public async
Task<IActionResult>', call this ‘Login’. And this one is going to take, again,
from the body, we will have the ‘LoginModel’. And then once again, we're going to try and get
a hold of the user with that particular username. Not going to call it ‘existingUser’ anymore,
because that's kind of obvious in this case, so just call it ‘user’. And then we've got to verify
that they've got the correct password. So we can say ‘if (user == null)’. So if we don't have a
user at all with that name ‘||’ and then ‘!await’ and then ‘_userManager’ once again, and then
‘CheckPasswordAsync’ and we pass in there the ‘user’ object and the password has been given
there. And so if either of those fails - so if we haven't got a user or they haven't got the correct
password - then we simply return ‘Unauthorized’, okay. Don't give them any information at all.
because you don't even want to tell them they've guessed the username wrong or guessed the password
wrong. If it's a hacker, you don't want any of that. And again, that's going to mean we need a
‘ProducesResponse ‘on there. So if we grab that, and put it in there, and that's going to be the
‘Unauthorised’, which is the 401 that we have there. Then if they've successfully logged in,
we need to give them some claims. So now, we're going to say ‘var authClaims’ - so authorization
claims – ‘= new List<Claim>’. And then the main claim that we need to give them is the claim that
they are who they say they are. So we do a ‘new Claim’. And then ‘ClaimTypes’ - just get hold
of the namespace there – ‘ClaimTypes.Name’. And that's going to be ‘model.UserName’.
And then also for security reasons, we make sure that we add another claim. And this one
is we use ‘JwtRegisteredClaimNames’ - and again, get hold of a namespace for that – ‘.Jti’ And that
one, we simply set to something unique. So this is to prevent what's called a replay attack, so that
we know that every set of claims has this unique stamp on it so it can't have this idea of a replay
attack. Again, details of that I may go into in a later video if people are interested in that.
Okay, so now we've got to take that list of claims and some other information and turn it into this
idea of a JWT - a JSON Web Token. And to do that, we've got to put a couple of things into
the appsettings file. So if we go in here, and in this we put in a new section called
‘JWT’ - whatever we want to call it, really, but that's the simplest one. And then in there, we
put a couple of things. We put a ‘ValidAudience’ and because we're only developing this, that's
going to be pretty simple. I'm just going to set this up as the URL on localhost to this site.
So if we go for ‘https://localhost’ and then if we just take a look in the launchsettings,
we can see that when we're launching this, that's going to be on port 7203. So if I just grab
that, and go back in there. So that's that one set up. And then I'm going to do exactly the same
thing, only this one is going to be ‘ValidIssuer’. But then the really important one is we need to have
a ‘Secret’. And basically, this is the encryption and decryption key for the JSON Web Token
we're creating. And that just has to be a long, effectively random string of characters. And
although there's lots of ways you can generate this one, quite an easy one we can do is if
we go to a site called ‘grc.com/passwords’. Then when we go there, we can see that just each
time we go there, it generates a brand new set of passwords. And the one that I'm going to go
for is this 63 digit, alphanumerical one. So if I just copy that, and then that is going to be my
secret that we have in there. And so that's going to be of use. And obviously, you want to keep
that secret, not let anyone know that because that could break the entire of your security.
So let's now go back to the controller. And so what we're going to do in there is we're going to
say ‘var key =’ and then we're going to say ‘new SymmetricSecurityKey’, we're going to use ‘UTF8’
encoding - get hold of the namespace again - and then we're going to use that to get as a
string of bytes. And then we're going to need to get hold of our secret. Now to get out of our
secret, I'm going to need to have access to the config file. So up here we'll put in another
injectable object. So I'll have a ‘private readonly IConfiguration _configuration’. We'll
add that in to the existing constructor because that'll get injected. And then down here, those
GetBytes, we're going to say are going to be on ‘_configuration’ and then we're going to read
out that ‘JWT:Secret’ that I just put in there. And that has a slight chance that could be
null if there was nothing in there, so we're going to just put the null coalescing operator and
throw a ‘new InvalidOperationException’ and just ‘Secret not configured’. Unlikely that that's
going to happen, but we've got to cover that just so it's safe in there. So that's going to be
if we throw an exception, that will be a response type of 500. So we'll pop that one in there just
for completeness. And need another close round brackets. So that's basically just taken that
and turned it into a string of bytes. And then we can say ‘var token = new JwtSecurityToken’. And
then in there, we're going to have the ‘issuer’. And remember that issuer is something else
we put in our configuration. So we'll say ‘_configuration[“JWT: Issuer”]’. We're going to
do the same sort of thing with the ‘audience’, which we also put in there. And then we're
going to put on this idea of the expiry time. So remember I said this token is only going to
be valid for whatever time after it's issued. So that's easy enough ‘DateTime.UtcNow’ and then
‘.Add’, and let's go for ‘Hours’. And let's give that three hours. So once I've logged in, this
will be valid for three hours after that. We then also put in those claims, so we say ‘claims:’
and then that set of claims that we just created, so ‘authClaims’ there. And then finally, we
have ‘signingCredentials:’ and we put in a ‘new SigningCredentials’ and that takes the key
that we just created based on our secret, and then also the algorithm we're
going to use. And we're going to go for SHA 256, which is a fairly standard one.
You could do it in different ways. As long as you're consistent when you're encoding and
decoding, that should be fine. So that's all of that. And then finally, we can return that token.
So we simply say ‘return’ and then it's an ‘Ok’ and then remember, we created that LoginResponse
precisely for this. So there's our LoginResponse and what we're going to do with that is simply
say the ‘JwtToken =’ and then we've got to do a ‘new JwtSecurityTokenHandler.WriteToken’ and then
passing that ‘token’. So that basically turns it into a string, which is what we want to send back.
And then also, remember, we've got expiration on there. And we can get hold of that just by
saying ‘token.ValidTo’. And so that's handing back the information we want. So that's now our
login. And I just noticed I got that wrong, what we have in the config file, were ‘ValidIssuer’
and ‘ValidAudience’. So let's run that one up. And we've already registered, so if I just go
to login and try it out. And then remember, my username was ‘Jasper Kent’ and my password was
‘Pa$$w0rd’. And if we execute that, then you can see there is response. So there is when it expires
in three hours time, but there is the token itself. So that is information, which we'll see
in the next video - because we're running out of time here - will allow us to get into that secure
controller for the book reviews or whatever it may be that we want to. But just before we finish here,
there's one way we can take a look at what's actually going on in that token. Because if we
go to another useful website called ‘jwt.io’, and that allows us to actually enter that JWT. So
you see there's a sample one there, but let's take the one that we had there, paste it in, and it
will partially decode it for us. So there's three sections hidden away in there, separated by those
dots. So the first section in red, that is simply Base64 encoding of the information it's telling us
there, so the algorithm that was used and the fact it's a JSON Web Token. Then the purple section,
again, is simply Base64 encoded, so there's no secrecy about it, it's just a compact form. But
that's telling us the information that we passed in there. So we can see in there the two claims
that we had of the name, that unique GUID that we created. And also we can see the expiration that
we've got in there as well. And then we've got in the blue bit, we've got the actual encrypted
part. And so what's in the encrypted part is essentially a checksum of that payload data, then
encrypted using the secret that I'd got generated and stored on the server. And so that means that
although it's perfectly possible to tamper with any of the information in the red section, or the
magenta section, when we then verify the token, the system would spot it because only it can
decrypt that secret section. And when it did, it discovered that the checksums don't add up
and therefore somebody has tampered with it. So what we've got here is a way that we can prove
that we are this person who logged in and what our claims are. And we'll have that valid
until the expiration time. Now, obviously, you as a user want to keep this token secret just
as much as you want to keep your password secret. So there is a responsibility on that anyone who
has this token can now impersonate you, but only to the extent that anyone with your username and
password can. So that's the security we've got. But we still haven't got to a point where we
can now actually send that back and allow us to get into the site. So we've still got
the situation that because I put that ‘[Authorize]’ on the controller, that we're
getting this error when we tried to access the data. And so that's what we're going to
be looking at next time. But in the meantime, hope you enjoyed that. If you did do subscribe,
do click ‘like’ and wait for the next video.