We've been looking at using JWTs alongside refresh tokens
so that when the JWT expires we can get a new one without the user having to enter their full
credentials, just having to send the refresh token along with the JWT. And we've done all
the work on the server for that. And so if we look at our server in Swagger, we now have these
four endpoints in terms of authentication. We've got the Register, we've got the Login, and the
Login - as well as giving you back the JWT - also now gives you back the refresh token. And then we
can use the refresh token - if the JWT’s expired - we pass them both in. And that will give us
back a new JWT. Or we can Revoke the refresh token so that even if somebody with a
refresh token tries to do a refresh, it's not going to be accepted, because we've revoked
it. And so that's what we had. And now we want to add that functionality to our Blazor application,
so that when that's running up, we can do these refreshes and make it seamless - from the user's
point of view - that we're actually sometimes doing a refresh. So the first thing we want
to do is we've got to go to our LoginResponse, and remember that now has to have the additional
property that is the RefreshToken - because that's what we're given back. So we'll add that
in there. Just copy that one, in fact, pop it down here. And we'll just call this the ‘RefreshToken’.
Okay, so that's the data we were already getting back there. Then the next thing we need to do
is we need to make sure that when we login, that refresh token gets stored alongside the JWT.
So if we go to our AuthenticationService, and then in here, when we do the login, we've also got to
store the RefreshToken. So in fact, what I need to do is create another key. So let's copy that one,
call this ‘REFRESH_KEY’ and pop that in there. And then down here, there we're saving the JWT. So we
also want to save the refresh token. And so that's stored in the same place as we're storing the
JWT. There are various places you might want to store the refresh token, and you can look online
to see the pros and cons of each one. But we're just going to put it in the session storage -
same place as the JWT. Now having done that, we now need to have this idea of actually calling
the Refresh. So remember, we've got this Refresh endpoint that allows us to use the refresh token
to get back a new JWT. And that uses a model that actually we can just steal from the service. So
here we've got the server and if we go in there and go into Authentication on here, there you can
see, we've got the RefreshModel. And that's what we send when we do a Refresh. As I mentioned in
an earlier video, because we've got C# both ends of it in Blazor, we could actually have a shared
DLL for this, but I'm just going to be lazy and do a copy and paste. So here I'm going to add into my
Models, we're going to call this a ‘RefreshModel’. And then in our I'll just paste in the class so it
exactly matches what we had at the other end. Then the next thing I need to do is I need to actually
make some use of this. So if we go to, again, the AuthenticationService, and in here, I'm going
to need to have another method that I'm going to call ‘RefreshAsync’. So this is going to be a
‘public async’, it's going to return a boolean to say whether the refresh has been successful
or not. We'll call it ‘RefreshAsync’. And then that's going to take no parameters. Then we're
going to create this model. So it'll say ‘var model = new RefreshModel’ that we just copied
over. And the data we need in there, remember, is the ‘AccessToken’ and that we can get
from the session storage. So we’ll do an ‘await SessionStorageService.GetItemAsync’.
And then what we're getting is going to be a ‘string’ and then we pass in the ‘JWT_KEY’, in
that case. And then the second thing we want is our ‘RefreshToken’. And that is going to
be very similar, except it's going to be the ‘REFRESH_KEY’. So that's that model sorted out.
Then we're going to send that off to the server. So we can say ‘var response = await’ and then
we do what we did before. So let's copy a bit of that. We do our ‘_factory.CreateClient’,
pop that in there. The difference is we're not doing a ‘login’, we're doing a ‘refresh’. And of
course the model is different, but we've called it ‘model’ so that works fine. So that will call
the new endpoint that we created last time. Then we've got to do the normal sorts of checks. So
we're going to say ‘if’ and then if it's not successful, then we're going to do a couple
of things. We're actually going to Logout. So remember, we had already written the Logout up
here, and it's going to remove the key. We should really also be changing that so that we remove
the REFRESH_KEY as well. So that will just keep that tidy. So what we also need to do in here is
actually call Revoke back on the service. So this, remember, is one of the big differences in
the way the security of a JWT works from the way refresh token works: the refresh token
can be revoked. And so given that we log out, we don't simply forget the refresh token in the
browser, we also tell the server to forget it so that even if someone's stolen it, they can't
make use of it. So what we additionally have to do there is we say ‘var response =’ and then
we're going to, again, steal some of that code. So paste that in. But then remember a few
differences. One is that's going to be the ‘revoke’. The revoke, we'd set up as a DELETE, and
therefore it doesn't have any kind of payload - it was just taking no parameters at all. So we do
that. And that will then revoke it on the server side. We should really make this bit conditional,
because we only want to do the revoke if the user is explicitly logging out. We don't need to do
the revoke if we're doing the logout because our refresh has failed. But let's just keep it
simple - but there is a slight overhead there, that's not really necessary. We don't really
need the response, this thing is either going to succeed - in which case it revoked it - or it's
going to fail, in which case we didn't have access anyway. But just so we can do something with
the response and see what's going on, what I'm going to do there is just a ‘Console.Write’ and
then just pass into that ‘Revoke gave response’ and then ‘response.StatusCode’. So notice that in
Blazor, if you want to do what would in JavaScript be a ‘console.log’, you can do the ‘Console.Write’
- or in this case do it async - and that will achieve the same thing. So we can just see what's going
on. So that's our LogoutAsync sorted, that we had called down here in the Refresh if we had
a problem - if we failed to do the refresh. The next thing we got to do there is we've got to
do a ‘return’ of ‘false’ because, remember, this is telling whoever called it whether that
Refresh has worked. On the other hand, if you've got a successful status code, that has worked,
okay, and so we now need to get hold of the new JWT. So we do a ‘var content =’ and then we can
say ‘await response.Content.ReadFromJsonAsync’. That's a generic and we're going to be reading
the ‘LoginResponse’ that we'd already written. Remember, both the login and the refresh give us
back a LoginResponse. So we've done that, then we need to do some other checks. And in fact, most
of these we can now pinch from the LoginAsync. So we need to check whether the content is ‘null’
and we need to get hold of those two items. So we'll copy that and paste it in here. We also
need to clear out the ‘_jwtCache’. Remember, we had this cache, just to save making those
asynchronous calls. But we need to set that to the new JWT. So we'll just say ‘_jwtCache =
content.JwtToken’. And then that's it. We can just ‘return true’ and everyone will be happy.
So that's the extra feature added to the service. And now we need to call that in response to any
kind of authorization failure. We're not going to know exactly why authorization has failed
- we're not getting very much information back on that - but we've got to take the guess that if
the authorization's failed, it's because our JWT has expired and therefore we need to do a refresh.
And we can do that actually, as part of the same AuthenticationHandler that we used previously in
order to inject the JWT into the header and send it off to the server. So that's what we have here.
We've got this AuthenticationHandler. And all of this code that we had there is dealing with the
request going out. But we can also use these to do extra processing on the response. Now, you might
say, would it be better to have this in a separate handler, because you're going to have multiple
handlers chained together? But they are both to do with authentication; they're quite tightly
bound together. So I think I'm going to put in the same one, it’ll just make the code a bit simpler;
you might want to put it separately. So what I'm going to do is rather than just returning that
‘base.SendAsync’ which is when it actually sends the request. I'm going to say ‘var response =’
and that response is this HttpResponseMessage, which is what we're ultimately returning. So after
that, I'm going to say ‘return’ that. But in the meantime, I'm going to check to see whether
the authorization failed, because if it did, then we know that we've got to do this refresh.
So what I'm going to do is, firstly I'm going to add another member on this. We're going to
have a ‘private bool’ which I'm going to call, ‘_refreshing’. And that will default to false. And
the reason I'm doing this is to prevent recursion, because when we do the refresh, we're making a
call to the server, which will come through here. And that might cause us problems. So what I'm
going to do is just check that we're not already refreshing. So ‘if (!_refreshing’. I then want to
check that we've actually got a JWT, because if we haven't got a JWT then clearly we haven't got an
expired JWT, so there's no point in attempting a refresh. So we'll say ‘!string.IsNullOrEmpty’
on the ‘jwt’. And then the other thing we want to check for is that the response did fail on
the basis of not being authorised - not for some other reason, in which case, again, what
will be the point in trying to do a refresh, if that wasn't the problem? So we'll just have
‘HttpStatusCode.Unauthorised’ and I'll just tidy that up by putting that into a using statement.
Okay, so if all of those things are okay, we know we want to attempt a refresh. So next thing we're
going to do is put in a ‘try’ block. And in here, the first thing we're going to do is have
that ‘_refreshing’ and set that to ‘true’. So that means that we won't come in here twice.
And then I'm going to have a ‘finally’ block where we're going to set that ‘false’. So a safe
way of making sure that we you don't come through this code twice, and the ‘finally’ means whatever
happens, we will get out of there. Then we need to call the AuthenticationService. So we've already
got the AuthenticationService injected in here, so we can say ‘if’ and then we'll do an ‘await’
and then ‘_authenticationService.’ and the ‘Refresh’ that we just wrote. It's not showing up
there. That's because I forgot to put it into the interface. So let's just do that. Let's take that
one there and then if we go up to the interface, pop it in there, then that will give us what we
want. And so back in our handler, we now should have the ‘RefreshAsync’ - doesn't take any
parameters. So that's just called that and if that's worked, okay, that means we should now have
the new JWT. So we're going to have to try again with a new one because we failed on the expired
one. So I'm going to say ‘jwt’ - we've already got the variable for that – ‘=’ and then we can just
call that same function. But now it'll give us the new one, which hopefully be working. And then
we're going to do basically the same thing we did here. So we're going to check that we've got a
JWT, we're going to check that it's going to the server and if it is, we'll add that new JWT into
the authorization header. And then we're going to repeat ourselves by reissuing this same request,
but with the new JWT in the header. So all of that code - we're really repeating ourselves to some
extent, we probably could have factored out, but I think it's clear, if we just lay it out like this.
So that's what we're going to do. We're going to resubmit the request but with the new JWT. And
that really should be all we need to do. So if we run that one up and drag it over. And at the
moment is failing. If we just pop up the console, we'll be able to see the console messages
coming in. If we pop up the server console, then we can see that that corresponds to what
we've got. So we've failed to retrieve the data because the JWT isn't present. If I do a login -
normal stuff - then we should see that we got the ‘Login called’, the ‘Login succeeded’. And we've
now, remember, got 30 seconds - that's all - to get our reviews done. So we can see there, we're
getting access to it. That's all working fine. And you can see it's telling us each time that
we're within the time available. So that's just the normal stuff of using the JWT. But as time
goes by - remember we've got the five second leeway on that - but eventually we're going to hit the
point where we get the failure. So we can see we got the ‘Onchallenge’. That's what told us that
the JWT had failed because it's expired. But then because of what we put into that handler on the
client, it called refresh. The refresh succeeded. We then resubmitted the request for the data with
a new JWT. And that worked. If we keep going again though, we're eventually going to expire on this
new token. And because the refresh token only had a minute, then we're going to have the refresh
called but we can see that that fails. So we can see there, that's where that was our message
‘Revoke gave response Unauthorized’ and so now we're not allowed in there - we haven't got a new
JWT. So we've got to go back and go through the whole thing again. Obviously doing that with
30 seconds and a minute is not a good idea, but you can see the benefits we can have with
both of those. The other thing remember we did is on our logout, we made it so that it
actually tidied everything up. So if we just check that, we can see that if we go to
Application and give that a bit more room, there we can see we've got both of the keys
in there. If I do a logout, they disappear. And also we can see we called the Revoke. So that
means it's disappeared from the database and so we wouldn't be able to use that at all, even if
someone had stolen that refresh token and stolen a JWT, because the refresh token is no longer in
the database, they couldn't get back in there. So that's the whole of that system of refresh. We
saw in the previous video, what's going on server. We saw here what to do in Blazor. As I mentioned,
Angular and React - I'll leave you to work that out for yourself unless there's real demand for
it, but I think we've covered enough of that. So I hope you enjoyed that. If you did do click
‘Like’, do subscribe, and I'll see you next time.