[Backend #20] How to create and verify JWT & PASETO token in Golang

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
Hello everyone, In the previous lecture, we have learned about token based authentication and why PASETO is better than JWT in term of security practice. Today we will learn how to implement both of them in Golang to see why PASETO is also much easier and simpler to implement compared to JWT. Alright, let’s start! First, I’m gonna create a new package called token Then create a new file maker.go inside this package. The idea is to declare a general token maker interface to manage the creation and verification of the tokens. Then later, we will write a jwt maker and a paseto maker struct that implements this interface. By doing so, we can easily switch between different types of token makers whenever we want. So this interface will have 2 methods: The CreateToken() method takes a username string and a valid duration as input. It returns a signed token string or an error. Basically, this method will create and sign a new token for a specific username and valid duration. The second method is VerifyToken, Which takes a token string as input, and returns a Payload object or an error. We will declare this Playload struct in a moment. The role of this VerifyToken method is to checks if the input token is valid or not. If it is valid, the method will return the payload data stored inside the body of the token. OK, now let’s create a new payload.go file. And define the Payload struct inside it. This struct will contain the payload data of the token. The most important field is username, which is used to identify the token owner. Then an issued at field to know when the token is created. When using token based authentication, it’s crucial to make sure that each access token only has a short valid duration. So we need an expired at field to store the time at which the token will be expired. Normally these 3 fields should be enough. However, if we want to have a mechanism to invalidate some specific tokens in case they are leaked, So we need to add an ID field to uniquely identify each token. Here I use the UUID type for this field. The type is defined in the google/uuid package, So we have to run go get command to download and add it to the project. As you can see here, The package has been added to the go.mod file. Let’s continue. I’m gonna define a function NewPayload That takes a username and a duration as input arguments And returns a payload object or an error. This function will create a new token payload with a specific username and duration. So first, we have to call uuid.NewRandom() to generate a unique token ID. If an error occurs, we simply return a nil payload and the error itself. Else, we create the payload Where ID is the generated random token UUID, Username is the input username, IssuedAt is time.Now(), And ExpiredAt is time.Now().Add(duration), Then we just return this payload object and a nil error, And we’re done. Now we’re gonna implement a JWT maker. We will need a JWT package for golang, So let’s open the browser and search for jwt golang. There might be many different packages, But I think this one is the most popular. So let’s copy its URL, and run go get in the terminal to install the package. OK, the package is installed. Now let’s go back to visual studio code. I’m gonna create a new file jwt_maker.go inside the token package Then declare a new type JWTMaker struct This struct is a JSON web token maker, which implements the token maker interface. In this tutorial, I will use symmetric key algorithm to sign the tokens, so this struct will have a field to store the secret key. Next, let’s add a function NewJWTMaker, That takes a secret key string as input, and returns a Maker interface, or an error as output. By returning the interface, we will make sure that our JWTMaker must implement the token maker interface. We will see how the go compiler checks this for us in a moment. Now, although the algorithm we’re gonna use doesn’t require how long the secret key should be. It’s still a good idea to ensure that the key should not be too short, for better security. So I will declare a constant minSecretKeySize = 32 characters. Then inside this function, we check if the length of the secret key is less than min secret key size or not. If it is, we just return a nil object and an error saying that the key must have at least 32 characters. Otherwise, we return a new JWTMaker object with the input secret key, and a nil error. Now you can see a red line here, because the JWTMaker object that we’ve created doesn’t implement the required methods of the token maker interface, which is the return type of this function. So in order to fix this, we have to add the CreateToken and VerifyToken methods to this struct. Let’s copy them from the Maker interface, and paste them here. Then let’s add the JWTMaker receiver in front of each method. Alright, now you can see that the red line is gone, since our JWTMaker struct has all 2 required functions of the interface. OK, let’s implement the CreateToken method. First we create a new token payload by calling NewPayload, and pass in the input username and valid duration. If error is not nil, we return an empty token string and the error. Else, we create a new jwtToken by calling the jwt.NewWithClaims function of the jwt-go package This function expects 2 input arguments. First, the signing method (or algorithm). I’m gonna use HS256 in this case. Then the claims, which actually is our created payload. Finally, to generate a token string, we call jwtToken.SignedString(), and pass in the secret key after converting it to byte slice. Here we have an error because our Payload struct doesn’t implement the jwt.Claims interface. It’s missing one method called Valid(). The jwt-go package needs this method to check if the token payload is valid or not. So let’s open the payload.go to add this method. The signature of this method is very simple, It doesn’t take any input argument, and only return an error in case the token is invalid. You can easily find this method in the implementation of the jwt-go package. The simplest but also the most important thing we must check is the expiration time of the token. If time.now() is after the payload.expiredAt, then it means that the token has expired. So we just return a new error saying: token has expired. Actually it’s better to declare this error as a public constant, so that we can check the error type from outside. I’m gonna call it ErrExpiredToken. And here, we just return this ErrExpiredToken. If the token is not expired, then we simply return nil. And that’s it! The Valid function is done. Now back to our jwt_maker.go file, we can see that the red line on the payload object is gone. Now as we’ve imported the jwt-go package, We should run go mod tidy in the terminal to add it to the go.mod file. The jwt-go version I’m using is 3.2.0 If you’re watching this in the future, it might be possible that you will use a newer version, such as 4.0, then the function & interface signatures might be different. However, the idea should be similar. OK, now the create token method is done. Let’s move on to the verify token. This will be a bit more complicated. First, we have to parse the token by calling jwt.ParseWithClaims and pass in the input token string, an empty payload object, and a key function. What is a key function? Well, basically, it’s a function that receives the parsed but unverified token, You should verify its header to make sure that the signing algorithm matches with what you normally use to sign the tokens. Then if it matches, you return the key so that jwt-go can use it to verify the token. This step is very important to prevent the trivial attack mechanism as I explained in the previous lecture. Alright, I’m gonna copy this function signature, and paste it here. Let’s set this input argument’s name to token, and its type should be jwt.Token. Then we just pass the keyFunc to this ParseWithClaims call. Now in the key function, we can get its signing algorithm via the token.Method field. Note that it types is a SigningMethod, which is just an interface. So we have to try converting it to a specific implementation. In our case, I’m gonna convert it to SigningMethodHMAC because we’re using HS256, which is an instance of the SigningMethodHMAC struct. This conversion can be successful or not. If it is not ok, then it means that the algorithm of the token doesn’t match with our signing algorithm So it’s clearly an invalid token. We have to return a nil key here with an ErrInvalidToken. I’m gonna declare this new error inside the payload.go file, the same place as the ErrExpiredToken. They are different types of error that will be returned by our VerifyToken function. OK, back to the jwt maker. If the conversion is successful, then it means the algorithm matches. We can just return the secret key that we’re using to sign the token after converting it to byte slice, and a nil error. OK now the key function is ready. Let’s continue with this ParseWithClaims function call. If it returns a not nil error, then there might be 2 different scenarios: Either the token is invalid or it is expired. But now things get more complicated when we want to differentiate these 2 cases. If we follow the implementation of the jwt-go package, We can see that it automatically calls token.Claims.Valid() function under the hood, And in our implementation of this function, we’re returning token expired error. However, jwt-go has secretly hiden this original error inside its own ValidationError object. So in order to figure out the real error type, we have to convert the returned error of the ParseWithClaims function to jwt.ValidationError Here I assign the converted error to the verr variable. If the conversion is ok, we use the errors.Is function to check if the verr.Inner is actually the ErrExpiredToken or not If it is, we just return a nil payload and the ErrExpiredToken. Otherwise, we just return nil and ErrInvalidToken. In case everything is good, and the token is successfully parsed and verified, We will try to get its payload data by converting jwtToken.Claims into a Payload object. If it’s not ok, then we just return nil and ErrInvalidToken. Else, we return the payload object and a nil error. And that’s it! The jwt maker is done. Now let’s write some unit test for it. I’m gonna create a new file jwt_maker_test.go inside the token package. Then let’s add a new function TestJWTMaker() that takes a testing.T object as input. First, we have to create a new maker by calling the NewJWTMaker() function And pass in a random secret key of 32 characters. We require no errors to be returned here. Next, we generate a username with the util.RandomOwner() function And let’s say the token valid duration is gonna be 1 minute. Let’s also declared 2 variables to compare the result later. The issuedAt time should be time.Now() And we add the duration to this issuedAt time to get the expiredAt time of the token. OK, now we generate the token by calling maker.CreatToken function And pass in the username and duration. Require no errors. And require the output token to not be empty. Next, we call maker.VerifyToken to make sure that the token is valid And also get back its payload data. Require no errors. And require the payload object to be not empty. Now we need to check all fields of the payload object. First the payload.ID should be not zero. Then the payload.Username should equal to the input username. We use require.WithinDuration to compare the payload.IssuedAt field with the expected issuedAt time we saved above. They should not be different by more than 1 second. Likewise, we compare the payload.ExpiredAt field with the expected expiredAt time in the same manner. And we’re done. Let’s run this unit test! It passed. Cool! So that’s how we test the happy case. Now let’s add another test to check the expired JWT token case. Similar as before, we first have to create a new JWT maker. Then we will create an expired token by calling maker.CreateToken, pass in a random username and a negative duration. We require no errors to be returned. And the token should not be empty. Now we will verify this output token. This time, we expect an error to be returned. And more specifically, it should be ErrExpiredToken. Finally, the output payload should be nil. OK, let’s run the test. It passed. Excellent! The last test we’re gonna write is to check the invalid token case, Where a None algorithm header is used. This is a well-known attack technique that I have told you in the previous lecture. First I’m gonna create a new payload with a random username and a duration of 1 minute. Require no errors. Then let’s make a new token by calling jwt.NewWithClaims with the jwt.SigningMethodNone and the created payload. Now we have to sign this token using the SignedString() method. But we cannot just use any random secret key here, because the jwt-go library has completely forbidden from using the None algorithm to sign the token. We can only use it for testing when we pass in this special constant: jwt.UnsafeAllowNoneSignatureType as the secret key. If you follow the implementation of this value, you can see that normally the None sign method is disallowed, unless the input key is this special constant. It basically means that you’re aware of what you’re doing. Make sure you only use it for testing, and not for production. OK let’s get back to our code. Require no errors We have to create a new JWT maker as in the other tests And now we call maker.VerifyToken to verify the token we’ve signed above. This time, the function should also return an error. And the error should be equal to ErrInvalidToken. The output payload should also be nil. We should remove this colon because both payload and err are not new variables. Alright, now let’s run the test! It passed! Awesome! So now you know how to implement and test JWT in go. Although I think the jwt-go package was quite well implemented in terms of preventing security mistakes, It’s still a bit complicated and difficult to use than necessary, especially for the token verification part. Now I’m gonna show you how to implement the same token maker interface but using PASETO instead. It would be much easier and cleaner than JWT. OK, let’s open the browser and search for paseto golang. Open its github page and copy the URL. Then run go get with this URL to download the package. Now get back to our project, I’m gonna create a new file: paseto_maker.go inside the token folder. Similar to what we’ve done with JWT, Let’s declare a type PasetoMaker struct Which will implement the same token maker interface, but use PASETO instead of JWT. We’re gonna use the latest version of PASETO at the moment, which is version 2. So the PasetoMaker struct will have a paseto field of type paseto.V2 And as I just want to use the token locally for our banking API, we will use symmetric encryption to encrypt the token payload. Therefore, we need a field to store the symmetric key here. OK now let’s add a function NewPasetoMaker, which takes a symmetric key string as input, and returns a Maker interface or an error. This function will create a new PasetoMaker instance. Paseto version 2 uses Chacha Poly algorithm to encrypt the payload, So here we have to check the length of the symmetric key to make sure that it has the correct size that’s required by the algorithm. If the key length is not correct then we just return a nil object and an error saying invalid key size. it must have exactly this number of characters. Else, we just create a new PasetoMaker object that contains paseto.NewV2() and the input symmetric key converted to byte slice. Then we return this maker object and a nil error. Again, here we see a red line under maker object because it’s not implementing the token Maker interface yet. So let’s do the same as what we’ve done for JWT maker. I’m gonna copy these 2 required methods of the token maker interface, And add the paseto maker receiver in front of them. OK, now the red line is gone. Let’s implement the create token method. Similar as before, we first have to create a new payload with the input username and duration If error is not nil, we return an empty string and the error to the caller. Otherwise, we return maker.paseto.Encrypt, And pass in the maker.symmetricKey, and the payload object. The last argument is an optional footer, which we don’t need, so I put nil here. And that’s it! Pretty short and simple, right? If we follow the implementation of this Encrypt function, We can see here, it is using chacha poly cipher algorithm, And inside this newCipher function, It also checks the input key size to make sure that it equals to 32 bytes. Alright, now let’s go back to our code and implement the VerifyToken method. It’s very simple. We just need to declare an empty payload object to store the decrypted data. Then call maker.paseto.Decrypt with the input token, the symmetric key, the payload and a nil footer. If error is not nil, we return nil payload an ErrInvalidToken. Else, we will check if the token is valid or not by calling payload.Valid(). If there’s an error, we just return nil payload and the error itself. Otherwise, we return the payload and a nil error. And that’s it! Very concise and much simpler than JWT, right? OK, now let’s write some unit tests. I’m gonna create a new file: paseto_maker_test.go inside the token package Actually the test would be almost identical to the one we wrote for JWT, So I’m just gonna copy it here. Change its name to TestPasetoMaker. Then here, instead of NewJWTMaker, we call NewPasetoMaker We don’t have to change anything else because PasetoMaker implements the same Maker interface as JWTMaker. Let’s run the test. It passed! Now let’s copy the test for the expired token case. Change its name to TestExpiredPasetoToken And update this call to NewPasetoMaker. Then run the test. It also passed. Excellent! We don’t need the last test because the None algorithm just doesn’t exist in Paseto. You can write another test to check the invalid token case if you want. I leave it as an exercise for you to practice. And that brings us to the end of this lecture. We have learned how to implement both JWT and PASETO using Go to create and verify access tokens. In the next video I will show you how to use them in the login API, where users provide their username & password, and the server will return an access token if the provided credentials are correct. Thanks a lot for watching this video and see you soon in the next lecture!
Info
Channel: TECH SCHOOL
Views: 3,194
Rating: undefined out of 5
Keywords: jwt golang, paseto golang, jwt go, paseto go, jwt, jwt token, paseto, paseto token, jwt authentication, json web token, platform agnostic security token, token based authentication, token authentication, golang web development, golang tutorial, golang, backend master class, backend course, backend development, backend tutorial, coding tutorial, programming tutorial, tech school
Id: Oi4FHDGILuY
Channel Id: undefined
Length: 23min 30sec (1410 seconds)
Published: Sat Feb 20 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.