Using JWT for Authentication in a Golang Application

Vonage Dev
codeburst
Published in
13 min readOct 9, 2020

--

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:

  1. A JWT is stateless. That is, it does not need to be stored in a database (persistence layer), unlike opaque tokens.
  2. The signature of a JWT is never decoded once formed, thereby ensuring that the token is safe and secure.
  3. 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:

  1. 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.
  2. The JWT might be hijacked and used by a hacker without the user doing anything about it, until the token expires.
  3. 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:

  1. 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.
  2. 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:

  1. The Access Token
  2. 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 the user_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.

--

--

Developer content from the team at Vonage, including posts on our Java, Node.js, Python, DotNet, Ruby and Go SDKs. https://developer.vonage.com