Using JWT for Authentication in a Golang Application

Introduction
A JSON Web Token (JWT) is a compact and self-contained way of securely transmitting information between parties as a JSON object, and they are commonly used by developers in their APIs. JWTs are popular because:
- A JWT is stateless. That is, it does not need to be stored in a database (persistence layer), unlike opaque tokens.
- The signature of a JWT is never decoded once formed, thereby ensuring that the token is safe and secure.
- A JWT can be set to be invalid after a certain period of time. This helps minimize or totally eliminate any damage that can be done by a hacker, in the event that the token is hijacked.
In this tutorial, I will demonstrate the creation, use, and invalidation of a JWT with a simple RESTful API using Golang and the Vonage Messages API.
Vonage API Account
To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.
This tutorial also uses a virtual phone number. To purchase one, go to Numbers > Buy Numbers and search for one that meets your needs. If you’ve just signed up, the initial cost of a number will be easily covered by your available credit.
A JWT is comprised of three parts:
- Header: the type of token and the signing algorithm used. The type of token can be “JWT” while the Signing Algorithm can either be HMAC or SHA256.
- Payload: the second part of the token which contains the claims. These claims include application-specific data(e.g, user id, username), token expiration time(exp), issuer(iss), subject(sub), and so on.
- Signature: the encoded header, encoded payload, and a secret you provide are used to create the signature.
Let’s use a simple token to understand the above concepts.
Token = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRoX3V1aWQiOiIxZGQ5MDEwYy00MzI4LTRmZjMtYjllNi05NDRkODQ4ZTkzNzUiLCJhdXRob3JpemVkIjp0cnVlLCJ1c2VyX2lkIjo3fQ.Qy8l-9GUFsXQm4jqgswAYTAX9F4cngrl28WJVYNDwtM
Don’t worry, the token is invalid, so it won’t work on any production application.
You can navigate to jwt.to and test the token signature if it is verified or not. Use “HS512” as the algorithm. You will get the message “Signature Verified”:
To make the signature, your application will need to provide a key. This key enables the signature to remain secure-even when the JWT is decoded the signature remains encrypted. It is highly recommended to always use a secret when creating a JWT.
Token Types
Since a JWT can be set to expire (be invalidated) after a particular period of time, two tokens will be considered in this application:
- Access Token: An access token is used for requests that require authentication. It is normally added in the header of the request. It is recommended that an access token has a short lifespan, say 15 minutes. Giving an access token a short time span can prevent any serious damage if a user’s token is tampered with. In the event that the token is hijacked, the hacker only has 15 minutes or less to carry out his operations before the token is invalidated.
- Refresh Token: A refresh token has a longer lifespan, usually seven days. This token is used to generate new access and refresh tokens. In the event that the access token expires, new sets of access and refresh tokens are created when the refresh token route is hit (from our application).
Where to Store a JWT
For a production grade application, it is highly recommended that you store JWTs in an HttpOnly
cookie. To achieve this, while sending the cookie generated from the backend to the frontend (client), a HttpOnly
flag is sent along the cookie, instructing the browser not to display the cookie through the client-side scripts. Doing this can prevent XSS (Cross Site Scripting) attacks. JWT can also be stored in browser local storage or session storage. Storing a JWT this way can expose it to several attacks such as XSS mentioned above, so it is generally less secure when compared to using `HttpOnly cookie technique.
The Application
We will consider a simple todo restful API.
Create a directory called jwt-todo
, then initialize go.mod
for dependency management. go.mod
is initialized using:
Now, create a main.go
file inside the root directory(/jwt-todo
), and add this to it:
We will use gin for routing and handling HTTP requests. The Gin Framework helps to reduce boilerplate code and is very efficient in building scalable APIs.
You can install gin (if you have not already) using:
Then update the main.go
file:
In an ideal situation, the /login
route takes a user's credentials, checks them against some database, and logs them in if the credentials are valid. But in this API, we will just use a sample user we will define in memory. Create a sample user in a struct. Add this to the main.go
file:
Login Request
When a user’s details have been verified, they are logged in and a JWT is generated on their behalf. We will achieve this in the Login()
function defined below:
We received the user’s request, then unmarshalled it into the User
struct. We then compared the input user with the one we defined in memory. If we were using a database, we would have compared it with a record in the database.
So as not to make the Login
function bloated, the logic to generate a JWT is handled by CreateToken
. Observe that the user ID is passed to this function. It is used as a claim when generating the JWT.
The CreateToken
function makes use of the dgrijalva/jwt-go
package, we can install this using:
Let’s define the CreateToken
function:
We set the token to be valid only for 15 minutes, after which time it is invalid and cannot be used for any authenticated request. Also observe that we signed the JWT using a secret( ACCESS_SECRET
) obtained from our environmental variable. It is highly recommended that this secret is not exposed in your codebase but rather called from the environment just like we did above. You can save it in a .env
, .yml
, or whatever works for you.
Thus far, our main.go
file looks like this:
We can now run the application:
Now we can try it out and see what we get! Fire up your favorite API tool and hit the login
endpoint:
As seen above, we have generated a JWT that will last for 15 minutes.
Implementation Loopholes
Yes, we can login a user and generate a JWT but there is a lot wrong with the above implementation:
- The JWT can only be invalidated when it expires. A major limitation to this is that a user can login then decide to logout immediately, but the user’s JWT remains valid until the expiration time is reached.
- The JWT might be hijacked and used by a hacker without the user doing anything about it, until the token expires.
- The user will need to log in again after the token expires, thereby leading to poor user experience.
We can address the problems stated above in two ways:
- Using a persistence storage layer to store JWT metadata. This will enable us to invalidate a JWT the very second the user logs out, thereby improving security.
- Using the concept of refresh token to generate a new access token, in the event that the access token expired, thereby improving the user experience.
Using Redis to Store JWT Metadata
One of the solutions we proffered above is saving a JWT metadata in a persistence layer. This can be done in any persistence layer of choice, but Redis is highly recommended. Since the JWTs we generate have an expiry time, Redis has a feature that automatically deletes data whose expiration time has been reached. Redis can also handle a lot of writes and can scale horizontally.
Since Redis is a key-value storage, its keys need to be unique. To achieve this, we will use uuid
as the key and use the user ID as the value.
So let’s install two packages to use:
We will also import those in the main.go
file as follows:
Note: It is expected that you have Redis installed in your local machine. If not, you can pause and do that before continuing.
Let’s now initialize Redis:
The Redis client is initialized in the init()
function. This ensures that each time we run the main.go
file, Redis is automatically connected.
When we create a token from this point forward, we will generate a uuid
that will be used as one of the token claims, just as we used the user ID as a claim in the previous implementation.
Define the Metadata=
In our proposed solution, instead of just creating one token we will need to create two JWTs:
- The Access Token
- The Refresh Token
To achieve this, we will need to define a struct that houses these tokens’ definitions, their expiration periods, and uuids:
The expiration period and the uuids are very handy because they will be used when saving token metadata in Redis.
Now, let’s update the CreateToken
function to look like this:
In the above function, the Access Token expires after 15 minutes and the Refresh Token expires after seven days. You can also observe that we added a uuid as a claim to each token. Since the uuid is unique each time it is created, a user can create more than one token. This happens when a user is logged in on different devices. The user can also logout from any of the devices without them being logged out from all devices. How cool!
Saving JWTs metadata
Let’s now wire up the function that will be used to save the JWTs metadata:
We passed in the TokenDetails
which have information about the expiration time of the JWTs and the uuids used when creating the JWTs. If the expiration time is reached for either the refresh token or the access token, the JWT is automatically deleted from Redis.
I personally use Redily, a Redis GUI. It’s a nice tool; you can take a look below to see how JWT metadata is stored in key-value pair.
Before we test login again, we will need to call the CreateAuth()
function in the Login()
function. Update the Login function:
We can try logging in again. Save the main.g
o file and run it. When the login is hit from Postman, we should have:
Excellent! We have both the access_token and the refresh_token, and we also have the token metadata persisted in Redis.
Creating a Todo
We can now proceed to make requests that require authentication using JWT. One of the unauthenticated requests in this API is the creation of todo request.
First let’s define a Todo
struct:
When performing any authenticated request, we need to validate the token passed in the authentication header to see if it is valid. We need to define some helper functions that help with these.
First we will need to extract the token from the request header using the ExtractToken
function:
Then we will verify the token:
We called ExtractToken
inside the VerifyToken
function to get the token string, then proceeded to check the signing method.
Then we will check the validity of this token, whether it is still useful or it has expired, using the TokenValid
function:
We will also extract the token metadata that will lookup in the Redis store we set up earlier. To extract the token, we define the ExtractTokenMetadata
function:
The ExtractTokenMetadata
function returns an AccessDetails
(which is a struct). This struct contains the metadata ( access_uuid
and user_id
) that we will need to make a lookup in Redis. If there is any reason we could not get the metadata from this token, the request is halted with an error message.
The AccessDetails
struct mentioned above looks like this:
We also mentioned looking up the token metadata in Redis. Let’s define a function that will enable us to do that:
FetchAuth()
accepts the AccessDetails
from the ExtractTokenMetadata
function, then looks it up in Redis. If the record is not found, it may mean that the token has expired, hence an error is thrown.
Let’s finally wire up the CreateTodo
function to better understand the implementation of the above functions:
As seen here, we called the ExtractTokenMetadata
to extract the JWT metadata which is used in FetchAuth
to check if the metadata still exists in our redis store. If everything is good, the Todo can then be saved to the database but we chose to return it to the caller.
Let’s update main()
to include the CreateTodo
function:
To test CreateTodo
, login and copy the access_token
and add it to the Authorization Bearer Token field like this:
Then add a title to the request body to create a todo and make a POST request to the /todo
endpoint, as shown below:
Attempting to create a todo without an access_token
will be unauthorized:
Logout Request
Thus far, we have seen how a JWT is used to make an authenticated request. When a user logs out, we will instantly revoke/invalidate their JWT. This is achieved by deleting the JWT metadata from our redis store.
We will now define a function that enables us delete a JWT metadata from redis:
The function above will delete the record in redis that corresponds with the uuid
passed as a parameter.
The Logout
function looks like this:
In the Logout
function, we first extracted the JWT metadata. If successful, we then proceed with deleting that metadata, thereby rendering the JWT invalid immediately.
Before testing, update the main.go
file to include the logout
endpoint like this:
Provide a valid access_token
associated with a user, then logout the user. Remember to add the access_token
to the Authorization Bearer Token
, then hit the logout endpoint:
Now the user is logged out and no further request can be performed with that JWT as it is immediately invalidated. This implementation is more secure than waiting for a JWT to expire after a user logs out.
Securing Authenticated Routes
We have two routes that require authentication: /login
and /logout
. Right now, with or without authentication, anybody can access these routes. Let's change that.
We will need to define the TokenAuthMiddleware()
function to secure these routes:
As seen above, we called the TokenValid()
function (defined earlier) to check if the token is still valid or has expired. The function will be used in the authenticated routes to secure them. Let's now update main.go
to include this middleware:
Refreshing Tokens
Thus far, we can create, use and revoke JWTs. In an application that will involve a user interface, what happens if the access token expires and the user needs to make an authenticated request? Will the user be unauthorized and be made to login again? Unfortunately, that will be the case. But this can be averted using the concept of a refresh token which means that the user does not need to log in again. The refresh token (created alongside the access token) will be used to create new pairs of access and refresh tokens.
Using JavaScript to consume our API endpoints, we can refresh the JWTs easily using axios interceptors. In our API, we will need to send a POST request with a refresh_token
as the body to the /token/refresh
endpoint.
Let’s first create the Refresh()
function:
While a lot is going on in that function, let’s try and understand the flow.
- We first took the
refresh_token
from the request body. - We then verified the signing method of the token.
- Next, we checked to see whether the token was still valid.
- The
refresh_uuid
and theuser_id
were then extracted, which are metadata used as claims when creating the refresh token. - We then searched for the metadata in the Redis store and deleted it using the
refresh_uuid
as key. - We then created a new pair of access and refresh tokens that will be used for future requests.
- The metadata of the access and refresh tokens were saved in Redis.
- The created tokens were returned to the caller.
- In the else statement, if the refresh token was not valid, the user will not be allowed to create a new pair of tokens. We will need to relogin to get new tokens.
Next, add the refresh token route in the main()
function:
Testing the endpoint with a valid refresh_token
:
And we have successfully created new token pairs. Great!
Send Messages Using the Vonage Messages API
Let’s notify users each time they create a Todo using the Vonage Messages API.
You can define your API key and secret in an environmental variable then use them in this file like this:
Then, we will define some structs that have information about the sender, the receiver, and the message content:
Then we define the function to send a message to a user below:
In the above function, the To
number is the number of the user, while the From
number must be purchased via your Vonage API Dashboard.
Ensure that you have your NEXMO_API_KEY
and NEXMO_API_SECRET
defined in your environment variable file.
We then update the CreateTodo
function to include the SendMessage
function just defined, passing in the required parameters:
Ensure that a valid phone number is provided so that you can get the message when you attempt to create a todo.
Conclusion
You have seen how you can create and invalidate a JWT. You also saw how you can integrate the Vonage Messages API in your Golang application to send notifications. For more information on best practices and using a JWT, be sure to check out this GitHub repo. You can extend this application and use a real database to persist users and todos, and you can also use a React or VueJS to build a frontend. That is where you will really appreciate the refresh token feature with the help of Axios Interceptors.
Originally published at https://www.nexmo.com/blog/2020/03/13/using-jwt-for-authentication-in-a-golang-application-dr