JWT Authentication in Go (Gin/Gorm)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] what's up tubers welcome to another edition of coding with robbie my name is robbie and in this video we're going to build a jwt authentication system in golang so we will be using some third-party libraries we're going to use gorm which is the top orm library for golang we're going to use gin it's the best web framework we're going to use the official bcrypt package and then we're going to use this jwt dash go library which is the most popular jwt uh package we're going to use go.m for uh environment variables and then i'll be using this compiled daemon package that just watches files or changes and restarts our project upon change so uh we're going to get into this right now so let's open up our terminal and uh let's just cd to our go workspace folder so let's go to my project folder source github robbie klein and uh let's make a new folder for our project i'll call mine go jwt and then i'm gonna cd into that folder and run go mod to uh create a go mod file so now we can install all the packages we're gonna need so let's just go through each one of these so for gorm we'll go to docs overview and we're gonna need this right here paste it in and we're gonna need a database driver i'm gonna be using postgres so i'll just copy the beginning part paste it in and type postgres and then we're gonna need gin so let's go to docs uh quick start and they got it right here let's grab that and i'll paste it in and then we're gonna need this decrypt package which comes from the crypto package so they have instructions right here let's copy the beginning part and i'll paste it in and type bcrypt so we just get the decrypt part so that should grab it and then we're going to need uh this jwt package so let's go to their github i think they have better instructions there scroll down and here's how we grab it so let's copy that and i'll just paste it in the terminal and then we want this go.m package so let's grab this with the command right here and i'll paste it in and then last up i want this compiled daemon package so let's uh grab it right here paste it in and then i'm also going to install this one so i can run it as a command line tool thing so go install and then the same command all right so we got everything installed now we're going to get started so let's open this up in vs code and let's create a main.go file and then in here it'll be package main and we need a main function so funk main and then let's just print line and just say hello for now save that now we're going to run it with that compile daemon package we installed so compile daemon and then we got to go dash dash command and once it builds it we want it to run dot slash and then whatever we named our go module so mine is go dash jw2 so that's going to build it and then run it and i get hello and now if i make a change it's going to automatically rebuild it and re-run it so i get hello too right there alright so now we're going to set up all these packages so there's kind of a lot of setup so i'm going to kind of go through it quick let's uh create a new folder called initializers and we're going to start with the environment variables so let's check out the amp package you just create a file in the root of your project put your variables in it and then you load it with this code right here so let's create dot env and i'll just go port is equal to 3000 save it and then let's go to initializers and create a new file called uh load whoops load m variables dot go and this is gonna be package initializers and we want a function called load m variables and uh just make sure to start it with the capital letter that lets you use it within other packages and then paste in the code we copied so this loads them and then if there's an error it lets us know now let's run this function within our main file so instead of doing it in main i'm going to do it within the init function which runs right before main so initializers dot load environment variables and that automatically imports it and we're running the function right there so that's good we got our environment variables loaded so now let's set up gin let's go to uh the gen quick start guide and they give us an example right down here let's copy it and um let's paste it in main so we're creating a gin app and then we have one route at slash ping that returns pong and we're starting it down here so let's go back to our browser and try localhost 3000 ping and we get the message so it's working good so now uh let's connect to a database so let's create a new file called connect to db.go and it's going to be package initializers frank connect to db and uh yeah let's look up how to connect to a database so we'll go to gorm and we'll go to connecting to a database and uh we don't have a database yet so you can create one at elephantsql.com just sign up create a free postgres database and you'll get to a page like this with all the credentials so let's go back to gorm so we're going to be using postgres so let's find the example for that and it's right down here i'm going to copy it i just want these bottom two lines and then let's paste it in our function here and then let's get our database credentials so i'll go to elephant i'll just copy the url right here and i'll paste it in and i'm just going to extract the details we need so here's the username here's the password here's the host and here's the database name and then the default port is five four three two now i'm gonna delete the time zone part there we go so this should connect to our database and then we're storing it right here as db but we really want this to be available anywhere in our project so i'm going to create a global variable for that so the var db is dot typestargorm.db and then let's create this error variable up here so var heir of type error and then instead of creating new variables we're going to assign it to the existing two variables we just created so db and air and then we gotta check for the error so let's go if air is not equal to nil let's um panic and say fail to connect to db so that looks good one other thing we can do is let's just move this to an environment variable so i'll copy all that go to env i'll just go db is equal to that go back here and to get the environment variable it's just os dot get m and then you just give it the key you want so ours is db and yeah that should connect to a database so let's run it in our main function so initializers.connect to db should be connecting let's check uh our terminal and i don't see any errors so it is working so next up we want to create a model so let's create a new folder called models and we're going to create our user model so let's go user model dot go and let's look up how to create a model so let's go to gorm and we'll go up and we'll go to declaring models and they give some examples here but the one i want to show is this one right here so they add this line gorm.model and this automatically adds these four fields which we're going to want so let's copy this example i'll paste it in this is going to be packaged models and then we're creating a user we're going to get those four columns and then we want ours to have an email of type string and a password of type string and then let's add some additional configuration the email so we can go back ticks gorm colon quotes and then there's some special options you can put in here and the one i want is unique so every email in our database will have to be unique so that's good and uh yeah we created a model and now we gotta migrate our database so it adds the users table and to do that if we go back to gorm and we go to the overview they show it um where do they show it right down here there's this auto migrate function where you just pass it a empty struct and it will create the table so let's copy that and we'll do it in another new file so initializer slash sync database dot go let's go package initializers function sync database i'll paste it in and ours is initializer but since we're still in the initializers package we can get rid of that beginning part and then we want to auto migrate our models.user there we go so let's run this let's go to main and we'll run it right after we connect initializers.sync database let's do that check our terminal i don't see any errors so it should have created the table so we can verify that let's go to elephant and copy the url and i have this table plus app where i can connect to a database so let's create new connection import from url and hit import and connect and you can see we have the users table with those four automatic columns and then our email and password all right so that's most of the setup so now we can start with our actual jwt stuff so let's start with a sign up function let's create a new folder called controllers and then we'll create a new file in there called users controller dot go and then uh this is gonna be package controllers and we want func sign up and this is going to receive the gen context so star gen dot context and uh yeah this is our sign up function so in here we wanna get the email slash password off wreck body we want to hash the password create the user and then respond so uh to do the first part we have to create a variable to store the data that's going to come in so let's go var body is going to be a struct and we're going to be expecting an email of type string and a password of type string and then to populate this variable with the data that came in you go c dot bind and you pass a reference to the variable so and body and then this can return an error so let's go if that is not equal to nil we know we have an error so we'll go in here and we'll go c dot json and we'll go http dot status and let's just do bad request and then we'll send a message gin.h and let's just go air failed to read body all right so this is going hey get these variables off the request body and then if it fails it's going to send this message and we want to add return so it doesn't continue doing anything else so that looks good so if we do make it down here we know that we have the body with email and password so let's uh hash the password so we're going to use that decrypt library we installed so it's decrypt dot and then the one we want is generate from password and then we have to give this a password as a byte array thing so let's just go body password and it automatically wraps it to convert it and then the second argument is the cost and the default is 10 and we'll just leave it at that and then this returns the hashed password and an error so let's get both of those we'll go hash and air like that let's check for the error so if error is not equal to nil if it's not nil we want to return early and say fail to hash password and then if we do get past that we now have a password hash so let's create the user with it let's go and look up how to do that so go to gorm i'll go to create and they give an example right here let's copy it and ours is models.user and then our user has an email and we'll give it the body.email and then we have a password and we'll give it uh the hash right here so hash but you see it's giving us an error it's saying ours is a string but it has to be that byte array thing so let's just go bite and wrap it like that uh i'm sorry i did it backwards we have the bite array and we want a string so let's get a string and put it in there so that fixes it and then uh this line right here creates it so ours is initializers.db dot capital db and uh this result object has a property called result.air so we want to go if result.error is not equal to nil then we know something went wrong so let's uh return early so we'll go error fail to create user and then we'll return if we get past here we know we got our user saved in the database and we can just respond so i don't think we really need to send anything to the user so let's just do a 200 success code so c dot json http status okay and then uh we'll go jan.h i'm just going to leave it blank and now let's connect this function to our router so let's go to main.go and then we'll make it slash signup and it's going to be a post request since we're sending in data and we're going to save and then instead of this function let's put our controller function so controllers dot sign up so that should be hooked up so now we're going to test it in postman so if you're off postman go to postman.com and download it it just lets you send http requests so let's create a new workspace i'll call it go and then it'll be a personal uh workspace i'll create it i'll create a new collection i'll call it authorization and then let's add a request in here and i'll call it sign up and then let's fill in the details so ours is a post request to http localhost 3000 slash signup and then we want to send stuff in the request body so let's go to body we'll select raw and then json and we want to send an email and a password [Applause] so save that let's send it and see if it works so i hit send and we get the 200 back and if we check our database i can refresh and it created it right here and it saved the password hash instead of the plain text and then watch if we try to create another user with the same email we get an error message so our unique uh config thing is working good so that's cool so now let's do login so let's go back to controllers let's create a new one down here funk login it's also going to get the gen context and in here we want to get the you not user email and pass off wreck body look up requested user compare sent in pass with saved user pass hash generate a jwt token and then send it back all right so this first part we did right above so let's just copy it to save some time and then if we're down here we know we got email and password so let's look up the user based on the email they sent in so let's look up how to do that i'll go to gorham again i'll go to query retrieving a single object and we want to do it by email so let's see we got um these are all by id here's one let's copy this and uh let's paste it in so ours is initializers.db and then we need a variable to store the user that's going to be looked up so let's go var user of type models.user so it's initializers with an s.db we want to find the first record where email is equal to and right here we'll do body.email which is the email they sent in so now let's make sure it actually found something so we can go if user.id is equal to zero then we know it didn't find a user so we can return early let's copy this and let's just go invalid email or password if we get past that we know we have the user so now we can compare the password they sent in with the actual password so to do that it's bcrypt dot and it's compare hash and password so the first argument we have to give it the hash password which is saved on user so user dot password and it's automatically going to wrap it in that byte thing and the second argument is uh the password they sent in so let's go body dot password it's automatically going to wrap it and then what does this return it returns an error if it was unsuccessful or they don't match so let's go air it's equal to the result of that and then we'll go if air oops not in javascript anymore if error is not equal to nil uh let's return early and send a message invalid email and password still is fine and then if we get past that we know the passwords match and we'll finally generate our jwt token so let's check out the docs for uh where is it this jwt package let's go to their github and uh they got a couple examples down here here's how to read a token and here's how to create one so we're creating one let's click in and we got an example right here let's copy it and i'll paste it down here and then we can leave all this here's where we put the data we want to encode within the token so we want to save the subject which is the user id and then i don't really care about this one let's do an expiration though though let's go exp and then we have to give it a unix timestamp so let's go what is it time dot capital now dot unix and then this would make it expire immediately so we got to add some time to this so let's go dot add and let's add time dot hour which is uh equal to that number right there which is one hour and then let's times it by 24 to get a day and times it by 30 to get a 30 days so that looks good so this like kind of sets it up and then right here is where we like encode and sign it and everything so we have to give this a secret key that's going to be used to encode and decode it so let's create an environment variable for that i'll just create a new one i'll call it secret and just make it equal to something that someone can't guess and it's hard to crack so here's some random characters let's go back to users controller and let's paste it in right here so to get it it's os dot get m and uh the key is secret and let me see what this expects i can't see it but uh okay we're getting the token string and an error so let's check for the error so if air is not equal to nil well then we want to return early and say fail to create token and then if we get down here we should have the token so what i'm trying to see is um if this has to be one of those byte strings or if it has to be a normal string so let's just leave that and we'll see if we get an error and then down here we gotta send it so let's go c dot json http status okay and let's go gen dot h and let's just send token and we'll send the token string so that looks good let's connect this to our oh add the trailing comma and then let's connect it to our router so let's go to main and i'll go down here i'll add a new one it's still a post and it's going to be the slash login and then uh controllers.login so let's go to uh postman again and create a new request so add request i'll call this one login it's gonna be a post to uh localhost 3000 slash login and then we got to give it a username and password in the body so let's go raw json i'll just copy it from the sign up request paste that in save it and let's run it and see what happens i hit send i get page not found so that's weird let's see post the slash login post to slash login why am i getting not found i probably have an error i do so let's see up here this is just annoying you have to go slash v4 so do that that fixes the error it rebuilds it and now we can try it so let's go to postman hit send i get invalid i don't know that doesn't make sense let's fix that so let's say failed where am i at uh down here let's make it say failed to create token and i think we do have to do the byte string so let's uh go like this i'll try it one more time so i'll go back to postman i'll hit send and now i get the token back so it's working so that's one way you could send it like this and then store it in local storage or whatever but everyone says cookies are better so let's do a cookie so down here uh instead of sending it like that let's just send nothing and i'll set up the cookie here so it's c set cookie same set same site and we want http dot same site lacks mode and then c dot how do you do it is it just cookie c dot cookie that returns it we want set cookie all right and then let's create a cookie so let's call ours authorization second argument is the value which we want equal to token string third argument is the max age and this is in seconds not milliseconds so 3 600 is one hour so let's times it by 24 for uh a day and then times it by 30. and then uh the next is the path let's just leave that empty next is the domain i'll leave it how it comes and then secure normally you would want true here but since we're on localhost i'm going to go false and then http only we want true that makes it so only the browser and our server can read it so save that so now we're sending a cookie let's try it out we'll go back to postman and i hit send i get the 200 back and now i can see it's sending the cookie with our token right there and we got the expiration date for one month and yeah it's all working good so we got sign up and login and now we just need some uh authorization middleware so we can secure routes so let's uh create a new function in here called validate and we'll get the gen context again and in here let's just go c dot json http status okay and let's just send uh what is it jan.h let's just send message i'm logged in and then let's connect this to our router so let's go to main i'll do a new one it'll just be a get request to slash validate and let's go controllers dot validate and let's just make sure it's working in postman so let's create a new uh request and it's going to be localhost validate and this time it's a get request so let's save it and hit send and we get i'm logged in so now we actually got to secure that route so let's create a new folder we'll call it middleware in here let's create a new file called require auth go and this will be package middleware funk require auth and we gotta do capital letters and this is also going to get the gin context and then in here let's just go fmt.printline and middleware and then to continue on from this middleware you just go to c dot next like that so now let's connect it to our validate route we'll go to main and uh right before the controller function and after the path we just put middleware dot require auth so that should work let's go to postman and we'll send it again and we get the message let's go to terminal and we see in middleware so it is running that function between everything so that's where we're going to check for everything all right so what do we got to do in here we want to get the cookie off request and then we want to decode validate it and then we want to check the expiration find the user with token sub attach to request and then continue alright so we're already continuing with the c dot next so let's get the cookie off the request you just go c dot cookie and you give it the name of the cookie you want so ours is called authorization and then this returns uh the value and an error so let's go token string and error is equal to that and then let's check for the error so we'll go if air is not equal to nil then we want to abort the request so c dot abort with status and we'll give it http status on authorized there we go if we get down here we know we got the token string so let's uh decode and validate it so let's see how to do that we'll go to that jwt uh package again and they got an example right here so let's just copy all of this and i'll paste it down here and i'm going to delete all their comments so right here we're passing it the token string and then it's just checking the signing method to make sure everything looks right and then right here we have to give it our secret key so ours was stored at os dot get m and we called our secret and then it has to be a byte array thing so let's uh convert it like this that looks good so this should parse our token and then down here it's going to look at the claims and everything so if that fails we want to abort also so let's copy this line and then if it does go through we have access to everything within our token now so let's move all this inside there and let's check the expiration so the expiration is saved as claims uh exp we just have to let go know that it's a float 64. and then we want to compare this to uh the current time so let's go time dot now and then this is a unix timestamp thing so let's go to unix and then uh so if the time now is greater than the expiration and we know it's expired and we want to abort uh so copy that line and then i think these are different types this is a n64 and this is a float 64. so let's just convert this so there's probably a cleaner way to do this but that's how i'm gonna do it and then uh yeah if that goes through we know it's not expired so let's find our user so we can look up how to do that let's go back to gorm query and retrieve a single object and let's say we want to find by id this time where is it here we go so let's copy this we'll put it right here we need a variable to store the found user so far user of type models user and then ours is initializers.db and we want to find the user where the id is claims sub which is the id that we saved on it and then let's just make sure we found a user so let's go if user.id is equal equal to zero we know it didn't find a user and we want to abort like that if we get down here we got the user so let's attach it to the request so you can just go c dot set and then we give it a key so our key will be user and it'll be equal to the user object right there and then we continue so let's uh try it out in postman i'll go back here and i'll hit send and i get through and i get the i'm logged in message so now if we go and uh log out so let's delete that cookie we'll go here i'll just delete it so now i don't have the authorization cookie and i hit send again and i'm still logged in so something's going wrong let's see what's going on oh okay here's the error so it's just annoying with this library you have to put slash v4 and go doesn't know to auto add it or anything so slash v4 now it's going to build successfully let's try it again without the cookie and i get the 401 unauthorized error and then let's log in again so now i got the cookie again and i go back here and i hit log in and it works and now if we go back to that validate request in here we have access to that user so the opposite of uh c.set is c dot get and then uh let's just give it the uh what we want we call it ours user and then this returns the user and then if it exists which i don't really care about the second argument i'll just do an underscore and now check it out if we put user here we have access to our user object so let's go back to postman hit send and we got the logged in user right there and then if you want to do stuff with this user you could go user and if you do dot it won't show you the fields because we have to let it know that it's models.user and now you can do id dot whatever and yeah that's pretty much jwt authentication so i hope this helped and i'll see you in the next video oh yeah like and subscribe and uh let me know you're there leave a comment tell me how great this video is and uh yeah see you later [Music] you
Info
Channel: Coding with Robby
Views: 65,945
Rating: undefined out of 5
Keywords:
Id: ma7rUS_vW9M
Channel Id: undefined
Length: 35min 26sec (2126 seconds)
Published: Wed Jul 20 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.