Blazor and JWT Refresh Tokens

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
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.
Info
Channel: Coding Tutorials
Views: 2,767
Rating: undefined out of 5
Keywords: JWTs, Refresh Tokens, Refreshing, Revoking, Captioned
Id: 0iJqh2lzEXo
Channel Id: undefined
Length: 16min 7sec (967 seconds)
Published: Fri Sep 15 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.