Refresh Tokens - BUILD A JWT AUTHENTICATION SERVER (ASP.NET CORE) #3

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
users can now register and then log in to get a jwt access token to authenticate with other servers but what's going to happen when that access token expires which we have set to expire in 30 minutes well the user is going to have to log in and see their username and password it's going to be a pain so what we need to do is introduce refresh tokens so that when the access token expires the user can send a refresh token to our authentication server and get back a new access token and if this is implemented correctly on the client side it's going to be a pretty seamless experience so let's get an expired jwt access token so we can test this out so i'm going to change my expiration minutes to 15 seconds 0.25 and i also need to update my authentication configuration because i take an integer and we now have a double so i log in get my access token let's get that real quick well i got 15 seconds paste that in here and first request we get okay so we did authenticate and now after waiting a little bit sending the request again and it still says okay so why is that well if we go into our data server and look at our token validation parameters one thing we need to add is a clock skew of time span zero so by default i think the clock skill is like five minutes so that means if your token expired four minutes ago it's still going to accept it because the token expiration is within that five minute clock skill so we're going to set that to zero so that when the token expires it is truly expired we get okay the first time i'll wait a little bit and there we go now we get unauthorized and the only way to re-authenticate would be to hit the login route on our authentication server again with our credentials get a new access token and pass it to the data server so instead of that it is now refresh token time so the first thing we need to do is actually generate a refresh token and give it back to the user and we're going to do that when they log in so our authenticated user response we are going to add a property for the refresh token it's just going to be a string again same thing as the access token and now we need to actually generate a refresh token up here for our user so we could use the access token generator if we take a look at that except the reason i don't want to do that is because we really do not need to sign these claims into our refresh token i'd also like to use a different secret key instead of the access taken secret so that if the access token secret got stolen all of my access tokens and refresh tokens wouldn't become invalid when i changed the secret and i would also like to use a much longer expiration minutes here preferably about a few months so instead of the access token generator we're going to create our own refresh token generator so just a class for that and similar to the access token generator we're going to have a method to generate a token and obviously we're going to be generating a jwt the only difference is the claims are going to be different and in fact on our refresh token we're not going to have any additional claims so for our refresh token generator we're going to have to get signing credentials again construct a jwt security token and then write the token so we are going to reuse all of this and just to demonstrate this a little bit better let's move the claims up here so basically everything right here is going to be reused in our refresh token generator so i could just duplicate all of this copy and paste it into my refresh token generator but i feel like we have a really great opportunity here to move this into its own class so i'm going to create a new class here for a token generator and this will also have a method to generate a token and it's going to hold all of this logic that we want to share between our access token generator and our refresh token generator so let's just cut that out move that into here import all this stuff and then we're going to get all of these configuration values from our method parameters so that the access token generator and the refresh token generator can pass in different values for things like the seeker key the expiration minutes and the claims which is important because they are going to have different values so let's move these into parameters so have a secret key we can just generate that the issuer and the audience the expiration minutes and last but not least the claims and i'm putting these last because i want them to be nullable because my refresh token generator isn't actually going to pass in any claims and one thing to note is that the jwt security token does accept null claims so let's start using this little helper class we have here so we'll get that through the constructor so now we have our token generator and now all we have to do is use that token generate and generate a token and pass in all of our values so we got the axis token secret the issuer the audience all coming from our configuration object and then we just pass in our claims as well so same thing just less duplication and before we forget we should register this with our services so the token generator and while we're here we can also add the refresh token generator as well and speaking of that refresh token generator let's head back there and this is going to need our authentication configuration and of course it's also going to need our token generator as well and obviously we are going to inject those to the constructor and this is going to be very simple to implement so i'm actually just going to copy the return statement in our access token generator paste that in our refresh token generator and the only difference is that we don't have any claims and we're also going to need a refresh token secret and every fresh token expiration minutes as well so let's generate these properties on our authentication configuration there we go here's our new properties and now all we have to do is add those to our app settings.json so we'll have a refresh token secret and the refresh token expiration minutes i got to do some math i think i want this to be like three months maybe all right here we go 131 400 minutes i didn't do the math i just typed it into google and now we need to update our refresh token secret because it's the same as our axe has taken secret so you could generate a new value using the website that i showed off last episode but just to keep this demo moving i'm just going to change this first character to a three incident before just so that the secrets are different and now we are ready to use our refresh token generator so let's go into our authentication controller and let's inject that so we'll have that in a field a refresh token generator add that to the constructor and we already have that registered in our startup.cs so now we can go ahead and use it generate a token and we're not passing in the user because we're not adding any user claims to the token but now we have our refresh token and now we can just send that back to the user yay so now when we log in as you can see we get our refresh token let's put that in the debugger jwt.io and here we go here's our payload so really all we added was the expiration the issuer and the audience and are not before is just the default right now so no additional claims and we'll see why that's okay in just a second so we have our refresh token now what the heck do we do with it how are we going to use this to get a new access token so we're going to need a new route for that so let's go into our authentication controller and this is going to be a post request to the refresh route so let's make a method to handle that we'll call it refresh and the user is going to have to send that refresh token in the body of the http request so we'll create an object for that a refresh request and we'll create that in our request folder over here in our models and all we're going to get in this request is the refresh token and might as well add some data annotations so this is going to be required so since we have data annotations we're going to start off by validating our model state so i'll just copy this and paste it down here so the very first thing i want to do is validate this refresh token make sure it's not expired validate the signing key so i'm going to create a new folder in my services for token validators and we're going to have a refresh token validator and we'll just have a single method on here to validate a refresh token and that's going to return true or false for if the refresh token is valid so validating jwtson.net really isn't that hard all we have to do is create a jwt security token handler so we'll just instantiate one of those and all we have to do is use tokenhandler and validate a token so we can pass in the string representing our token that's going to be a refresh token and then we're going to need token validation parameters we'll get those in a second and it's also going to output a security token that represents the validated token which we're actually not going to use but we do have to specify the output parameter and this method also returns a claims principle so it decodes the jwt which might be useful if you have information signed into those claims such as what we have in our access token but in this case we are not going to be using those claims and as you can see it throws a bunch of exceptions so if the signature is invalid if it's expired it's going to throw an exception so that said we're going to surround this in a try catch and if we get an exception then we're just going to return false because our token is not valid but otherwise we're going to return true so we need token validation parameters let's get a variable for those we'll generate that variable up here we can move this out of the try catch let's do that now token validation parameters sounds familiar doesn't it and that's because in our data server we actually use token validation parameters to configure our jwt authentication so what i'm going to do is just copy these and then we can use that in our validator and we don't have an authentication configuration right here but we can inject one through our constructor i'm just gonna call it configuration actually say rename all this and now instead of the access token secret we're gonna use our refresh token secret and now we can use this to validate our refresh token so let's go into our startup.cs we're gonna have to register this so the refresh token validator and now we can inject that into our authentication controller get it through the constructor and now we can finally validate our refresh tokens so we will use our refresh token validator validate the refresh token on our refresh request and if the token is not valid then we're going to return bad request and we'll say we got to make a new error response got to stay consistent with those error objects and we'll just say invalid refresh token of course we could change this validate method so that it would tell us if the token was expired or the signature was invalid but this is good enough for now we're just going to figure out if it's invalid so we've made it this far we now have a valid refresh token but we have an issue who is the user who sent this refresh request and how are we going to generate a new access token for them if we don't know who it is so there's really two solutions to this we could sign the user's id into the refresh token and get that id when we validate the refresh token but i actually don't want to do that because this refresh token is powerful if the refresh shouldn't get stolen then the person that stole the refresh token is going to be able to constantly refresh get new credentials and we would have no way to invalidate this refresh token unless we changed the signing key and that's kind of our main goal we don't want to be changing the signing key because that's going to invalidate every single refresh token so what can we do to invalidate a refresh token without changing the refresh token secret key we can store the refresh token in something like a database for example which we'll implement later and then if the refresh token doesn't exist in our authentication server storage or database then the token is not valid so that said if a refresh token gets stolen to invalidate the token we wouldn't have to change our secret key we would just simply remove it from our storage or our database and then that refresh token couldn't be used by some kind of imposter to keep getting a new access token and if we're going to be storing the refresh token in the database to validate it then we can also store that refresh token along with the id of the user that owns that refresh token and if we have that user id then we can query for that user by id and then in this refresh method we can finally generate a new access token for that user so in our services we're going to have a refresh token repository let's create a folder for those refresh token repositories and we're going to interface this because we're going to implement this using a database later so we're going to have the i refresh token repository and the first thing we need is a way to create or save a refresh token so we'll have a create method on here so we're not just going to have a string for the refresh token here because we want to store the user's id along with the refresh token so we're going to create an object to hold all that and i'm just going to call it refresh token so it's going to be like a dtl and we're going to create that in our models folder so a class for a refresh token and we're going to have a string for the refresh token that we're storing we mentioned we want the user id that's a guide and i actually can't call this refresh token because that's the name of the class we'll just call it token and then we'll also give this a unique identifier as well and now let's use that in this repository and let's actually implement this interface so same thing we did for the user repository we're just going to have an in-memory refresh token repository implement that interface we'll have a list up here to store all of these refresh tokens that we create just instantiate that up here refresh tokens and now this create method it's also going to give the refresh token its unique identifier and then we'll add that refresh token to our list of refresh tokens and we made this a test because we're going to implement it asynchronously when we have a database so all we can do here is just return task completed task just to satisfy this return type and let's register this in dependency injection so an i refresh token repository as the in-memory refresh shaken repository gonna have to inject that in our controller so now when we log in and we generate that refresh token we're gonna have to store it in our refresh token repository so let's create that dto for the refresh token set up all of these properties so we got our refresh checking right here and the user id we have our user that we retrieved from the login and now just use that refresh shipping repository and create the refresh token dto and that'll save it in the repository and now that it's saved when we refresh we're going to need a way to query that refresh token dto and the only way we can query that is using the refresh hooking that we get in our refresh request so on a repository we're going to have a method to get a refresh token and we're going to query this refresh token using the actual refresh token string so we'll call this get by token and we'll pass it the refresh token let's go ahead and implement that so we can use first or default on our list and get the first refresh token that has a matching token value and we'll just return that we have to use from result so we can satisfy this task return type and there we go now we can query for the token so let's go ahead and do that so we'll get a refresh token dto use get by token pass in the token from our refresh request and now this could return null if the refresh token didn't exist in our list so we're going to have to check for that and if it was not we're going to send another bad request for an invalid refresh chicken although actually if it wasn't found maybe we should do a not found i feel like it could go either way it's always a little bit tricky with authentication because a lot of times you really don't want to give too much information but now we have our refresh token dto and on this dto we have the user id so now we can query for this user by id and generate new tokens for them so to get that user by id we're going to use our user repository and we do not have a get by id method so i'm going to have to create one of those but we are going to pass in the user id and we'll put that into a variable it's going to be async let's generate that method nice so i got the signature right for us and let's implement that before we forget so we just copy one of these other methods and change this to id and user id so now back in our controller this user could also be gnaw so if the user's gnaw then we're going to return we'll go for another not found here and we'll say user not found this time as the error message but now if we're past this we have a user the refresh token was valid so now we can finally generate new tokens for them and this is actually going to be very similar to how we generated tokens up here so we generate an access token for user then a refresh token save the refresh token and give back this authenticated user response so instead of just duplicating all this what i'm going to do is create a class that does all of this for me so in my services i'll have a new folder here we'll call this authenticators and we'll create a class called an authenticator it took me a while to come up with this name because i needed something kind of generic and we'll just have a single method on here it's going to give us back that authenticated user response and i don't really love getting back a response object from this class ideally i would have like an authenticated user and then map authenticated user to the response object but just keeping it simple and we'll call this method authenticate and it's going to take in the user that we want to authenticate so let's go ahead and cut all this out paste it in here and we're not going to be returning okay because we're not in a controller and we're going to have to inject all of these services through the constructor so let's create fields for all those there we go and then generate that constructor and since we're injecting things let's go ahead and register that in our startup.cs the authenticator and now we're ready to use this and since we're going to be using our authenticator we're going to inject that and that authenticator is going to take care of the access token and refresh token generation so we don't actually need those classes in our controller so that's nice kind of getting rid of those low-level dependencies and i'm just going to completely regenerate this constructor now and let's use that authenticator so that gives us an authenticated user response by authenticating our user and then we can just return okay with that response and for the refresh method same thing so we just copy these simple two lines of code paste those down there and now we're successfully taking the user's refresh token validating the token and then generating new tokens for that user so i log in i get my access token let's do this real quick and then i authenticate with my data server there we go we get an okay but then finally the token expires so now i need to use my refresh token to get a new access token so let's duplicate this tab go to the refresh route and then for our body we need a refresh token which we got right here and let's send this request there we go we get new tokens let's grab this access token use it on our data server and now we are authenticated again and now this expires so now back on this refresh route i can send this refresh request again and get a new access token and paste that in here and that works so we kind of an issue here and that's you can use a refresh token multiple times and that might not be a big deal because we get a new refresh token back anyways but i still feel like it's unnecessary and we should delete the refresh token after it's used so that's no longer valid so what we're going to do is after we get the refresh token back from our repository we're simply just going to delete it so let's use our refresh token repository and delete a refresh token and actually we can just delete by id so we'll just pass the id to this delete method and let's generate this method there we go so it generated that correctly and let's implement it so we're going to have to return task completed task and then let's delete that refresh token pretty simple since we have a list we can use remove all and just pass in a predicate for the refresh tokens that we want to get rid of and we want to get rid of every fresh token with the matching id so now the refresh token isn't going to be able to be used multiple times all right so first attempt we use the refresh token successfully try again up nope invalid refresh token now and i also want to show off an expired refresh token so let's change this expiration to 15 seconds so if i refresh this we hit this exception when we go to validate the token and the exception is the token is expired so this validation does indeed work so what if the refresh token gets stolen whoever steals that token is going to be able to constantly refresh over and over again keep getting refresh tokens keep getting access tokens and just completely destroy the real user's life so we already established we're not going to change the secret key because that would invalidate every single refresh token that exists so what we need to do is give the user a way to invalidate their refresh tokens and we're going to classify that as logging out and we will implement that next time but for now we implemented refreshing capabilities so all we did was generate a refresh token stored the refresh token in a database so that we can invalidate whenever we need to without having to change the secret key and then we send that refresh token back to the user and they can hit this refresh route with the refresh token we validate the token make sure it exists in our database delete it once it's used then we get the user associated with that refresh token and give them back new tokens that's the main summary if you have any questions criticisms or concerns be sure to leave them below in the comments section but other than that leave a like or subscribe for more thank you
Info
Channel: SingletonSean
Views: 9,845
Rating: undefined out of 5
Keywords: asp, net, core, jwt, auth, authentication, server, api, rest, controller, mvc, web, application, programming, tutorial, learn, register, login, logout, refresh, token, access, sign, verify, hash, password, username, email, data, annotation, validation, model, state, error, message, clean, code, service, .net, restful, design, patterns, architecture, software, development, security, require, visual, studio, c#, project, setup, solution, postman, body, http, request, response, object, client, micro, services, azure, cloud, microservice, how, to
Id: ei19cwME1mE
Channel Id: undefined
Length: 21min 24sec (1284 seconds)
Published: Thu Dec 24 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.