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!