Over the past several videos we've been looking
at using JSON Web Tokens for authentication and authorization. And a very closely associated
concept that lots of people have been asking me to go into is the idea of refresh tokens. Because
the thing about the JWT is that once you've got hold of it - if somebody has managed to steal it,
for example - it entirely of itself can give you access to the web service. And the only limitation
on that is that we have that expiration time that in the examples I've been giving you, I'd set
to three hours. So if somebody does steal it, they've still got a limit on the amount of time
they've got access for. And so the shorter we make that expiration time, the more secure we're
being because we'll risk fewer of those problems. The disadvantage to that, however, is if we
have a shorter expiration time, then when the Web Token does expire, you've got to re-enter your
credentials - your username and password - to get a new JWT. And so that's inconvenient. So we've
got this compromise between security by making the expiration time short and convenience by making it
longer. And the refresh token is a way to help us with that compromise. Because what we do, as well
as issuing the JWT - the access token - we issue this thing called a refresh token, which has a
longer expiry than the JWT. And therefore when the JWT does expire, rather than having to enter our
full credentials to get a new one, we can offer up the refresh token. And that will give us back
a new JWT, if everything's working okay. Now that may sound like no difference at all; why not just
make the JWT have the longer expiry rather than having this separate refresh token? But it does
give us some advantages. It's not perfect, but it makes that compromise better. The first advantage
is there are two of them. So even if somebody had stolen your JWT, they haven't got hold of your
refresh token or vice versa. And you need both of them to do the refresh. So when you send off
the refresh token, you've also got to have an expired JWT. Otherwise, both of them won't work
without the other. And then the other advantage you've got - which is very different from a JWT
- a refresh token can be revoked. So if you know your system is compromised, or simply if you've
logged out, you can revoke the refresh token. And therefore even if somebody has it, it'll be
rejected because we know they're not allowed to use it. So those are the two big advantages. We'll
see a few more as we look at the details of it. So let's dive in and look at the technology of this.
I'm going to do this video on what's happening on server side and then the next video, I'll show you
what's happening client side. I'm just going to do this in Blazor because that seems to be the most
popular one that I've done. But I'm sure we can work out how to do it in Angular and in React.
So let's take a look what we've got, and I've just made a few changes to the existing server
simply really, I've just put in some logging. So if we look at the AuthenticationController, you
can see I'm just putting a log when we register and if we're successful, and a log when we log in
and that sort of thing. And then also I've put in on the Program here, I've put in some logging for
the actual authentication. The way we can do this, you can see down here where had the AddJwtBearer,
and I've got the options in there. The new bit I've done is on these two events, the OnChallenge
- so that's basically if the access fails, you haven't got a valid JWT - and the OnTokenValidated
- that's what happens when it does. And then you can see I've just called this LogAttempt and it
just gives us a bit of information. Particularly what it gives us is the current time on the server
and the expiration of the token. So I'm just going to do one more thing here, which is actually
change the timeout to something rather shorter. So when we do the login, previously, we had in this
GenerateJwt, we had add three hours. But now I'm just going to change that to adding 30 seconds, so
if we go to ‘AddSeconds’ and make that 30 seconds, just so we're not waiting around to see
the expiry. And so if we run that up, there we can see the Swagger that we've got. And
also if we just take a quick look at the console, this is where we'll see those logging messages
coming in. I've turned off the other messages just so it's not too messy. And so what we can do here
is if I, for example, try to just get the book reviews, then we can see we got that OnChallenge.
And there's no JWT present at all, because that's not working. But if I were to do my login and use
the same old login that we've been using for ages, then we can see it's ‘Login called’ - takes
a second or two for the first time … ‘Login succeeded’. And we've got back our JWT
and as before, I can now pop that into the Authorize up here. And that will now be sent
off in the header with every request. And so now if I try to get the data, we can see that
we've got the token validated we can see the current time as 9:23:36 and expiration
time - not long – 9:23:48. And if we just pop that back and execute again, we
can see that it's allowed us to come in there again. But actually something rather odd
has happened here. Because while I was talking, we actually went beyond the expiration. So the
system time is 9:23:58, but the expiration was 9:23:48. And the reason for this is because of a
concept known as Clock Skew. What you need to bear in mind with this is what I've done, it's a little
bit unusual in that I've got a single server; that is both my authentication server, giving
the login and that sort of thing and also is my data server giving me the book reviews. And more
commonly these would be on different machines; you'd have possibly a third-party organisation
that's doing your authentication for you, and they will give you the JWT that then works
on your site. And we can think of that in terms of anything with this AuthenticationController
could be on a different server from the book reviews. But once you've got that, then you
get the possibility that the system clocks on the authentication server and your server are not
in sync. And so, particularly if you've got short expiry times, you've got to have a bit of leeway
on the timeout comparing the expiry time to the current time, and that's the thing called Clock
Skew. And the default that you get with ASP.NET is a rather large five minutes. And so it's
advisable to shorten that to some extent. Again, exactly how short you make it, you've got to make
decisions on that, but I'm going to shorten it right down to five seconds. So if we go into
the Program, and then here in the options, when we have the TokenValidationParameters on
to that, we can add this ‘ClockSkew’ and change that to just five seconds. So ‘new TimeSpan(0, 0,
5)’. And that might be a bit short, but we want to just get things moving along quickly. And so now
if we run that up, and once again, let's log in and execute that and get hold
of the token, pop it in here. And then let's start accessing the data. Then we can see we've got a validated there. And
in that case, we're within the time limit just. But now, if we try that, again, we can see we've
got the OnChallenge, which means it's failed. And we can see we get into the 401 Not Authorised,
and the system time is more than five seconds beyond the expiration time. So we've got a very
tight time there. And that's going to get very irritating if we have to keep logging on every
30 seconds. Not realistic, but whatever the time may be, we want to have these refresh tokens
to make life easier. So let's put that in. So let's go back to the code and stop that. And the
first thing to notice about a refresh token that makes it different from a JWT is it does actually
require extra storage in the database. So a JWT, remember, once it's been sent to the client, the
client sends it back to the server, the server verifies it simply by using the encryption on
the JWT. That's the only way it knows that it's correct. It doesn't need to make any reference
to a database, which is something that also makes that process faster. But with a refresh token, we
do need to put it in a database so that it can be revoked, as we discussed. So let's do that. Let's
go to our LibraryUser. So remember, this is the data type derived from IdentityUser that actually
puts data in the database. And what we're going to add to that is a couple of things, we're going to
have a property of type nullable string - so it'll be a nullable column in the database - and we'll
just call that ‘RefreshToken’. So that's where the token goes. And then also stored in the database,
we're going to have a property of type ‘DateTime’, which is going to be the ‘RefreshTokenExpiry’. So
unlike the JWT, the expiry is not stored embedded in the token; the token is just a unique ID and
the expiry is looked up in the database. So that's what we've got there, then we've got to do the
work of adding that to the database. So simply enough, we just do our ‘add-migration’. And we'll
call that ‘AddRefreshToken’. So we'll let that run and then we'll do an ‘update-database’ and
that will put that into the database. Not going to bother to check that; believe me, that's in there.
Now we've got to make use of that. So what we've now got to do is in the AuthenticationController,
we've got to issue a refresh token as well as a JWT. So let's do that. So the first thing I'm
going to do is just pop into here a ‘private static’ method that's going to return
a ‘string’. And it's going to be called ‘GenerateRefreshToken’. And all the token is
going to be is a random number, effectively. So I'm going to say ‘var randomNumber’. And we're
going to have this initially as an array of bytes, so ‘new byte[64]’. So that should be big enough.
And then I'm going to use not the regular random number generator - just Random that you get in
the System namespace - because although that's good enough for certain situations, like gaming
and that sort of thing maybe, it's not regarded as secure. Those random numbers are somewhat
predictable. Now, it'd be very difficult to predict them, but it's regarded as a risk. So there is a
cryptographic random number generator that is much better at this and much more secure. So we're
going to say ‘generator =’ and then we've got this ‘RandomNumberGenerator’ from ‘Cryptography’
and that is simply used as a factory to create that. And then once we got those two, then we
can say, ‘generator.GetBytes’ and then we just put that ‘randomNumber’ into there. And that
will populate it with just this random number. And then finally, we want that as a string.
So we ‘return’. And we'll do a ‘Convert’ and then we just want to turn this into base
64, as we've seen before. So that's our random number turned into a string. And that's going
to be unique every time. Then we're going to make use of that, so now we need to make sure
that that gets returned with the other bits and pieces when we do the login. And so remember,
the login returns this LoginResponse. So let's alter that so that it's also got the refresh
token. So we'll put in here, just a ‘prop’. It's going to be a ‘required string’ and
we'll call this ‘RefreshToken’. I might for the convenience for the end user also put
another one in for the refresh token expiration, just like we've got for the JWT expiration,
but that just complicates matters. Sometimes you want to do that, but I'm going to leave it
out. Then back in our AuthenticationController, you can see we're getting an error there. So now
what we've got to do is in our Login, we've got to use that method I just wrote. So we're going to
say ‘var refreshToken =’ and then just use that ‘GenerateRefreshToken()’. So that'll give it us
back. We then got to do a couple of things with it. We've got to, remember, store this in the
database. And we've just added onto our 'user' object, we had the ‘RefreshToken’ - that's what I
just put in there. And then also, we've got to on our user object, set the ‘RefreshTokenExpiry’.
And again, we can choose whatever we like. The basic idea is it should be longer than the JWT
expiry. What I'm going to do is simply set that to one minute. That's a ridiculously short time for
real, but it's greater than our 30 seconds, it'll just mean we can demo this more easily. So we'll have
‘UtcNow.AddMinutes’ and just add the one minute onto there. And then we've got to store that in
the database. So we can do that with an ‘await’ and then we've already got our ‘_userManager’ and
we just do an ‘UpdateAsync’ and then pass in the user that we've altered, so that will be stored
in the database. And then finally, we've got to pop that into the LoginResponse. So if in here,
we just do the ‘RefreshToken = refreshToken’ and that's that all done. Let's just run that up, see
what's happening there. So if we now do our Login, and execute that, then we can see we're getting
back that refresh token. So just the base 64 encoded version of what we had. And if we take
a quick look in the database, then here in AuthenticatedLibrary, look at the tables. If we
look in the User table, and scroll over, there we can see on the end, there's the RefreshToken.
And there's the expiry that we gave it. And it's probably already out of date, because we only gave
it a minute, but we can see that that's working. Okay, now we need to start making use of that.
And we've got to provide another endpoint on the AuthenticationController that will actually accept
this refresh token and give us back the new JWT. So let's do that next. So what we're going to
do is back in here, we're going to put in this new endpoint. And we're going to make this a
POST for similar reasons to the way we made Login a POST. So it's another way of logging in,
really. And so we're going to call this ‘Refresh’. I'm going to give it a few response types just
so that Swagger can give us a nice view of what's going on. So it's going to have ‘Unauthorised’,
it's going to have an ‘InternalServerError’ and it's also going to have obviously enough ‘OK’ if
it succeeds. So that's what we've got there. And then it's going to be a ‘public async’ it's going
to be a ‘Task’ returning an ‘IActionResult’ and it's going to be called ‘Refresh’. And the data
that comes in is going to be coming in through the body, because obviously, we don't want to send the
refresh token in the URI, because that's not going to be secure. So it needs to come from the body.
And in fact, we need a new type for this. So let's go back to Authentication and add in here, a new
class that we're going to call ‘RefreshModel’. And the ‘RefreshModel’ is just going to take a
couple of things. As we said, you need two things to do refresh: you need the refresh token, but
you also need the JWT. So we're going to have a ‘required string’ and I'll call this ‘AccessToken’
- that's the JWT, just to distinguish it from the refresh token. And then we'll have another
one of those that we’ll call RefreshToken’. And so that's the data that's going to be sent
in when we do a Refresh. Back in the controller, that's going to be ‘[FromBody]’ and then
‘RefreshModel model’. So that's our data coming in. Just so we can see what's going
on, let's log that. So we'll just put in here ‘LogInformation’ and then simply ‘Refresh called’.
And then what we need to do is find out who it is that's trying to do the refresh. Now you'll
notice we have not put an [Authorize] on this, because this is for someone who is not authorised
because their JWT has expired. So we've got to get who it is and we get that from the JWT that's
passed in. So we're going to have to write a method to do this. And so what I'm going to do
is we'll have a ‘private’ method that returns this idea of a ‘ClaimsPrincipal’ - or not,
so make that optional. And then we'll call this ‘GetPrincipalFromExpiredToken’ and pass
in the ‘token’. And then we need to create some ‘TokenValidationParameters’ so that we can
have a look at the internals of this token. And we do that basically by copying the ones that
we had in the Program. So here, we set up these ‘TokenValidationParameters’. So we'll start out
by copying those and will just in here, say ‘var validation =’ all of that. Or at least that's
a good starting point - need to change a few things. One thing is our configuration is no
longer coming from the builder, we've already got a ‘_configuration’ injected in there, so we
can put that in there. And in there, we also need to get hold of our secret. So that again, we had
here in the Program, again, reading it from the configuration. So we'll put that in there. And
we've got the possible error if that goes wrong. But things we don't need - the one thing we don't
need here that's really important - is we don't want to have the lifetime validated, because the
whole point is this has expired. So we don't need the ClockSkew, because it's not even going to
look at that. We just can say ‘ValidateLifetime’ and then that ‘= false’. Okay, so what that
means is, it's still going to do the check on the security with the encrypted bit, but it's
not going to reject this on the basis of it being expired. It will reject it if anything else is
wrong. And so then we need to use that to generate the actual token. So all we now do is we say
‘return’ and then ‘new JwtSecurityTokenHandler’. So this is what's been used internally when we
were logging in previously, but now we're doing it explicitly and we use that to validate
the token. And then we pass in the ‘token’ and the ‘validation’ parameters that we've just
created. And then also, that returns the actual security token - so the security token that would
have been decomposed from the string version we put in. But actually, we're not interested
in that, so I’ll just put a discard on that, because we don't need it. So that's now got all of
that working. So that's what we can have and we can make use of it up here. So we're going to say
‘var principal =’ and then we can have just this ‘GetPrincipalFromExpiredToken’ and we can pass in
‘model.AccessToken’ and that should have got in there. Now we need to check that that's actually
worked - that has given us a valid token. So what we're going to do is say ‘if’ and then firstly,
check whether we had a ‘principal’ at all, because that could come back null. Then we're
going to see if that has an ‘Identity’ and then we're going to see if the ‘Identity’ has a ‘Name’.
And then we're going to test whether the ‘Name is null’. So if any of those have failed, then that
whole thing will come out as null. And in that case, we're simply going to bail out, so we're
going to do a ‘return Unauthorized()’ because something's gone wrong. And as ever, you don't
really want to tell your end user anything about what's gone wrong, because they may be trying to
somehow hack your system. So having done that, we then need to get hold of the actual user
object. So we can say ‘var user’ and again, we can get this from the ‘_userManager’ and we
can just do a ‘FindByNameAsync’ and we've already got that name because we just checked that it was
there. And let's not forget to ‘await’ that one. And then want to check, firstly, that we've
got a ‘user’ at all because it could be that that's an invalid user still. So ‘if’ the
‘user is null’. Then we also want to check that the user's refresh token is the same
one as the one that we've just had passed in. So ‘model.RefreshToken’, because those
have to match - that's what we're checking, to see this is the correct refresh token. And
then finally, we need to check that the refresh token hasn't expired. So we need to say ‘||
user.RefreshTokenExpiry < DateTime.UtcNow’. So three things we're checking. We’re checking
we've got a user at all, we're checking that that user matches the refresh token that's being handed
and we're checking that the refresh token hasn't expired. Notice again, though, we're not getting
the expiry from the token like we do with the JWT, we're getting the expiry from the database
where we set it up when we logged in. So if any of those fails, again, we're going to ‘return
Unauthorized()’. But if it works, we're now in the position to give them back the JWT. So we can
say ‘var’ and then ‘token =’. We've got this function that we'd already used in the regular
login, this ‘GenerateJwt’ and we pass into that simply the username, which again we saw it was
‘principal.Identity.Name’. So there's our token. And then the last thing we have to do, we’ll
again just log that so that we know that's worked. So if it fails, we'll just see the ‘Refresh
called’ and nothing else, because it'll leave on those returns. But if it succeeded, we'll
get this far. And then all we have to do is basically the same that we did with the Login. So
if we just grab that, put that in there, change that to ‘model.RefreshToken’ because we're just
giving them back the same refresh token. Now you don't always do it like this. Sometimes you'll see
situations where if you do a Refresh, you refresh the refresh token as well as the JWT. So you give
yourself a bit more time on the refresh token. Whether that's secure or not, it's up to you to
decide. I've decided to just stick with the same one, really so we can see how it eventually
expires. But you could do it by generating a new one with a new time, but we haven't gone for
that. Okay, so that's what we've got there. And that really gives us the basic functionality
that we want. So if we now run that up … Again, we'll put the two of these side
by side so we can see what's going on. And just to prove it's not working to start with,
so let's open that and do a try it out. And so the JWT is not present. Now let's go for a Login. And
so I'll try that one out, normal sort of thing, ‘Jasper Kent’ and ‘Pa$$w0rd’. Execute that. ‘Login
called’, ‘Login succeeded’ and so we've got back our JWT. So let's make use of that. Got to be
fairly quick here, because we haven't got long. So we put in ‘Bearer’ and then paste that in there.
‘Authorize’, close that and then back down to the get. And you can see that was fine. So our data's
come back, because we were within the time limit of 30 seconds. If we hit that again, we're still
there, though we're getting quite close. Give it a little bit longer, because remember, we’ve got
the five second Clock Skew as well still there. Now it's expired. So now we've got to do
the Refresh. So if I go to the Refresh, and try that out. Go to be fairly quick here. So
the access token I'd already copied, but then I also need to get hold of the refresh token. So
that's what we had there. Pass that in there, execute that we've got the refresh succeeded.
And so it's given us back a new JWT. So now if I grab that one, and put it into the Authorize
- so log out with the old one, put in the new one, authorize that. And then we should find back
down here that our execute is working again, but only for another 30 seconds. So I had to
do that pretty fast, just because I've set those limits so short. But you can see in a bigger
system, if, say, your JWT was set for three hours, and maybe your refresh token was set for 24 hours,
that would mean that you could get back in without having to re-enter your credentials, which is
probably going to be a safer way to go. There's one last thing I want to add on here though, because
remember we said one very important thing about a refresh token is it can be revoked, unlike a JWT.
So let's pop that one in there as well. So what I want to do is on the AuthenticationController,
I'm going to add a new endpoint for revoke. So let's just steal a little bit of that to make
life simpler to start with, and paste that in there. And so first thing is, this is not going
to be a POST; this is going to be a DELETE, because we're actually going to be deleting the
refresh token from the database. So ‘HttpDelete’, we'll call it ‘Revoke’. Also, I'm going to make
this one authorized. So this is something that will be a bit different if we have a separate
authentication server from the data server, because you wouldn't actually be logged into the
authentication server typically. But we are logged in here, so we can just use ‘[Authorize]’. So
you're only allowed to do the revoke if you're logged in, and you're a valid user. So we've got
that. The status codes are going to be the same. We'll change that to ‘Revoke’ as well. And then
there are going to be no parameters, because we're simply going to revoke for the currently logged
on user. So that's what we've got there. Again, we'll start with just a quick log message so we
can see where we are. So ‘LogInformation’ and we'll just put on there ‘Revoke called’. And then
the first thing we need to do is get hold of the identity of the logged in user. And that's easy
enough, we can say ‘var username =’ and then it's available on the ‘HttpContext’. So if we get hold
of that, and look at ‘User.Identity’. Identity may be null so we’ll just put a ‘?’ in there and
then get hold of the ‘Name’. So that will give us the name of the logged in user, if there is one.
That's the kind-of odd thing here. Because we're authorized, we know the must be one. But as far
as C# is concerned, Identity still could be null. So we've got to put that in there. And then we
can put a check for that anyway, so just put ‘if (username is null)’ then we're going to ‘return
Unauthorized()’. And then we're going to from the username get a hold of the user. So we can say
‘var user =’ and we can say ‘await _userManager’ and then ‘FindByNameAsync’. And that's going
to take the username that we just got hold of. And then having got hold of the user, again we're
going to check we’re there. So ‘if (user is null) and then another ‘return Unauthorized()’.
And then having got to that point, all we now need to do is remove the refresh
token. So we say ‘user.RefreshToken = null;’ And then we just need to update that, so another
‘await’ and then on the ‘_userManager’ we can say ‘UpdateAsync’ and pass in the ‘user’. We don't
need to do anything with the RefreshTokenExpiry, because if we haven't got a RefreshToken,
it's never going to look at that. So we can just leave it like that. So having got that
far, we'll do a another quick log, and just say ‘… succeeded’. And then the only thing we need
to return is just ‘Ok()’ to say that's worked - no data really to go back. So that's our Revoke
put together. And so now if we run that up … And again, let's compare both the console and the
Swagger. And so first thing we'll do is log in. So our usual way of doing this … and ‘Pa$$w0rd’. And so we'll execute that. We'll just see the
message ‘Login called’ and ‘Login succeeded’. That gives us back our JWT. Let's take that
and use that for authorization. So ‘Bearer’ and then paste that in. And we should now see that
we've got access to the data there. So if we just look at that, we're getting the data coming back.
But now let's call Revoke. So all we need to do is go to the Revoke that we've got here that DELETE.
Click ‘Try it out’. Nothing particular to do, just execute that. We can see ‘Revoke
called’ and ‘Revoke succeeded’. Now, if we tried to do a Refresh, so let's go in
there and try that out. I've already got the JWT, so that one goes in there. But if I scroll back
up to the Login and get hold of the refresh token, and then paste that in there, then we can see
that we get back a 401 because we no longer had a refresh token in the database. And therefore, it
couldn't match anything against the one we passed in. And if we just take a look at the database,
so just go into the SQL Server Object Explorer, have a look in there - just do a quick refresh.
And we can see over here on the right, it's still got the old expiry date hanging around,
but that doesn't matter. It's the fact that we've got NULL in there is why it wasn't working.
So that was the things we need to do on the server side to start using refresh tokens. So we
needed to make sure we've got extra columns in the database. We've got to generate the refresh
token and its expiry date when we do a login. Store them in the database. Hand back the refresh
token. And then we needed a Refresh endpoint that accepted both the JWT and the refresh token and,
if everything was in order, gave us a new JWT, optionally a new refresh token, but we didn't do
that. And then also you need a Revoke so that you can remove that. And that means we've now got
an explicit way of logging out. So previously, we logged out just by throwing away the JWT. But
obviously, if someone had acquired it, they could still use it. Whereas now we've explicitly
logged out, they may be able to use the JWT, but they won't be able to use the refresh token
because it's now invalid. Next time, we're going to see how that can all come together, because
it's quite fiddly doing all that in Swagger. So we're going to add the functionality to a Blazor
application and we can see how all that works. So I hope enjoyed that. If you did, do subscribe,
do click ‘Like’ and I'll see you next time.