https://d226lax1qjow5r.cloudfront.net/blog/blogposts/using-jwt-for-authentication-in-a-golang-application-dr/Blog_JWT-Golang_Authentification_1200x600-2.png

Using JWT for Authentication in a Golang Application

Published on October 23, 2020

A JSON Web Token (JWT) is a compact and self-contained way for 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.

What Makes Up a JWT

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”:

A JSON Web Token broken down using JWT.ioA JSON Web Token broken down using JWT.io

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 7 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 to 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 with 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. Before we get started, it is assumed as a pre-requisite that you know some of the basics of Go.

Create a directory called jwt-todo, then initialize go.mod for dependency management. go.mod is initialized using:

go mod init jwt-todo

Now, create a main.go file inside the root directory(/jwt-todo) as the entry point to your application and add this to it:

package main

func main() {

}

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:

go get github.com/gin-gonic/gin

Then update the main.go file:

package main

import (
 "github.com/gin-gonic/gin"
)

router := gin.Default()

func main() {
  router.POST("/login", Login)
  log.Fatal(router.Run(":8080"))
}

So, we start by importing gin, then creating a router. We're going to be sending a POST request to log in, and you'll notice the Login function which has not been defined yet (we'll get to that).

In an ideal situation, the /login route takes a user’s credentials, checks them against some database, and logs them in when the credentials are valid. But in this API, we will create a dummy user that is defined within the code. To create a sample user in a struct, add this to the main.go file:

type User struct {
 ID uint64       `json:"id"`
 Username string `json:"username"`
 Password string `json:"password"`
 Phone string `json:phone`
}

// a dummy user
var user = User{
 ID:          1,
 Username: "username",
 Password: "password",
}

Login Request

I mentioned that we'd get to the missing Login() function, which runs when we want to compare the incoming user data in the request with one retrieved from a data source. With dependency injection, *gin.Context pulls the request into a variable c which we then pass the User struct by reference into ShouldBindJSON() to make sure the JSON is valid. The code looks like this:

func Login(c *gin.Context) {
  var u User
  if err := c.ShouldBindJSON(&u); err != nil {
     c.JSON(http.StatusUnprocessableEntity, "Invalid json provided")
     return
  }

  // Compare the user from the request, with the one we defined:
  if user.Username != u.Username || user.Password != u.Password {
     c.JSON(http.StatusUnauthorized, "Please provide valid login details")
     return
  }

  token, err := CreateToken(user.ID)
  if err != nil {
     c.JSON(http.StatusUnprocessableEntity, err.Error())
     return
  }
  c.JSON(http.StatusOK, token)
}

We received the user’s request, and unmarshalled it into the User struct. We then compared the input user with the one we defined 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:

go get github.com/dgrijalva/jwt-go

Let’s define the CreateToken function:

func CreateToken(userid uint64) (string, error) {
  var err error
  //Creating Access Token
  os.Setenv("ACCESS_SECRET", "jdnfksdmfksd") //this should be in an env file
  atClaims := jwt.MapClaims{}
  atClaims["authorized"] = true
  atClaims["user_id"] = userid
  atClaims["exp"] = time.Now().Add(time.Minute * 15).Unix()
  at := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims)
  token, err := at.SignedString([]byte(os.Getenv("ACCESS_SECRET")))
  if err != nil {
     return "", err
  }
  return token, nil
}

We set the token to be valid only for 15 minutes, after which, 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 environment variable. You should always have secrets in an environment variable file exclusive to your local, production or testing stack, make sure you never commit secrets into your code.

Thus far, our main.go file looks like this:

package main

import (
	"log"
	"net/http"
	"os"
	"time"

	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
)

type User struct {
	ID       uint64 `json:"id"`
	Username string `json:"username"`
	Password string `json:"password"`
	Phone    string `json:"phone"`
}

var dummyUser = User{
	ID:       1,
	Username: "username",
	Password: "password",
	Phone:    "49123454322",
}

func CreateToken(userid uint64) (string, error) {
	var err error

	os.Setenv("ACCESS_SECRET", "jdnfksdmfksd") //this should be in an env file
	atClaims := jwt.MapClaims{}
	atClaims["authorized"] = true
	atClaims["user_id"] = userid
	atClaims["exp"] = time.Now().Add(time.Minute * 15).Unix()
	at := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims)
	token, err := at.SignedString([]byte(os.Getenv("ACCESS_SECRET")))

	if err != nil {
		return "", err
	}

	return token, nil
}

func Login(c *gin.Context) {
	var u User
	if err := c.ShouldBindJSON(&u); err != nil {
		c.JSON(http.StatusUnprocessableEntity, "Invalid json provided")
		return
	}
	// Compare the user from the request, with the one we defined:
	if dummyUser.Username != u.Username || dummyUser.Password != u.Password {
		c.JSON(http.StatusUnauthorized, "Please provide valid login details")
		return
	}
	token, err := CreateToken(dummyUser.ID)
	if err != nil {
		c.JSON(http.StatusUnprocessableEntity, err.Error())
		return
	}
	c.JSON(http.StatusOK, token)
}

func main() {
	router := gin.Default()
	router.POST("/login", Login)
	log.Fatal(router.Run(":8080"))
}

We can now run the application:

go run main.go

Now we can try it out and see what we get! Fire up your favorite API tool and hit the login endpoint:

Making a request using PostmanMaking a request using Postman

As seen above, we have generated a JWT that will last for 15 minutes.

Implementation Loopholes

Yes we can log in a user a 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, and then decide to log out 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 re-login after the token expires, thereby leading to a 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 a the user logs out, thereby improving security.

  2. Using the concept of a 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 offered 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 expiry time, Redis has a feature that automatically deletes data whose expiration time has 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:

go get github.com/go-redis/redis/v7
go get github.com/google/uuid

We will also import those in the main.go file like so:

import (

  "github.com/go-redis/redis/v7"
  "github.com/google/uuid"

)

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:

var redisClient *redis.Client

func init() {
  // Initializing Redis
  dsn := os.Getenv("REDIS_DSN")
  if len(dsn) == 0 {
     dsn = "localhost:6379"
  }

  redisClient = redis.NewClient(&redis.Options{
     Addr: dsn, // Redis Port
  })

  _, err := redisClient.Ping().Result()
  if err != nil {
     panic(err)
  }
}

The Redis client is initialized in the magic init() method. 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 house these tokens definitions, their expiration periods and uuids:

type TokenDetails struct {
  AccessToken  string
  RefreshToken string
  AccessUuid   string
  RefreshUuid  string
  AtExpires    int64
  RtExpires    int64
}

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:

func CreateToken(userid uint64) (*TokenDetails, error) {
	td := &TokenDetails{}
	td.AtExpires = time.Now().Add(time.Minute * 15).Unix()
	td.AccessUuid = uuid.New().String()

	td.RtExpires = time.Now().Add(time.Hour * 24 * 7).Unix()
	td.RefreshUuid = uuid.New().String()

	var err error

	// Creating Access Token
	os.Setenv("ACCESS_SECRET", "jdnfksdmfksd") //this should be in an env file
	atClaims := jwt.MapClaims{}
	atClaims["authorized"] = true
	atClaims["access_uuid"] = td.AccessUuid
	atClaims["user_id"] = userid
	atClaims["exp"] = td.AtExpires
	at := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims)
	td.AccessToken, err = at.SignedString([]byte(os.Getenv("ACCESS_SECRET")))

	if err != nil {
		return nil, err
	}

	// Creating Refresh Token
	os.Setenv("REFRESH_SECRET", "mcmvmkmsdnfsdmfdsjf") //this should be in an env file
	rtClaims := jwt.MapClaims{}
	rtClaims["refresh_uuid"] = td.RefreshUuid
	rtClaims["user_id"] = userid
	rtClaims["exp"] = td.RtExpires
	rt := jwt.NewWithClaims(jwt.SigningMethodHS256, rtClaims)
	td.RefreshToken, err = rt.SignedString([]byte(os.Getenv("REFRESH_SECRET")))

	if err != nil {
		return nil, err
	}

	return td, nil
}

In the above function, the Access Token expires after 15 minutes and the Refresh Token expires after 7 days. You can also observe 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 log out from any of the devices without 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:

func CreateAuth(userid uint64, td *TokenDetails) error {
	at := time.Unix(td.AtExpires, 0)
	rt := time.Unix(td.RtExpires, 0)
	now := time.Now()

	errAccess := redisClient.Set(td.AccessUuid, strconv.Itoa(int(userid)), at.Sub(now)).Err()

	if errAccess != nil {
		return errAccess
	}

	errRefresh := redisClient.Set(td.RefreshUuid, strconv.Itoa(int(userid)), rt.Sub(now)).Err()

	if errRefresh != nil {
		return errRefresh
	}

	return nil
}

Make sure that you add the import for the standard library strcov function at the top of the main.go file.

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. Is a nice tool. You can take a look below to see how JWT metadata is stored in a key-value pair.

Using Readily to see the stored metadata in RedisUsing Readily to see the stored metadata in RedisBefore we test login again, we will need to call the CreateAuth() function in the Login() function. Update the Login function as follows:

func Login(c *gin.Context) {
	var u User
	
	if err := c.ShouldBindJSON(&u); err != nil {
		c.JSON(http.StatusUnprocessableEntity, "Invalid json provided")
		return
	}

	// Compare the user from the request, with the one we defined:
	if dummyUser.Username != u.Username || dummyUser.Password != u.Password {
		c.JSON(http.StatusUnauthorized, "Please provide valid login details")
		return
	}

	ts, err := CreateToken(dummyUser.ID)

	if err != nil {
		c.JSON(http.StatusUnprocessableEntity, err.Error())
		return
	}

	saveErr := CreateAuth(dummyUser.ID, ts)

	if saveErr != nil {
		c.JSON(http.StatusUnprocessableEntity, saveErr.Error())
	}

	tokens := map[string]string{
		"access_token":  ts.AccessToken,
		"refresh_token": ts.RefreshToken,
	}

	c.JSON(http.StatusOK, tokens)
}

We can try logging in again. Save the main.go file and run it. When the login is hit from Postman, we should have:

Checking the access and refresh token reponse in PostmanChecking the access and refresh token reponse in PostmanExcellent! We have both the access_token and the refresh_token, and also have 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:

type Todo struct {
  UserID uint64 `json:"user_id"`
  Title string `json:"title"`
}

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:

func ExtractToken(r *http.Request) string {
	bearToken := r.Header.Get("Authorization")
	// Normally Authorization the_token_xxx
	strArr := strings.Split(bearToken, " ")

	if len(strArr) == 2 {
		return strArr[1]
	}

	return ""
}

We then write the function to verify the token:

func VerifyToken(r *http.Request) (*jwt.Token, error) {
	tokenString := ExtractToken(r)
	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		// Make sure that the token method conform to "SigningMethodHMAC"
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
		}
		return []byte(os.Getenv("ACCESS_SECRET")), nil
	})

	if err != nil {
		return nil, err
	}

	return token, nil
}

Make sure to add the import for the standard library fmt library. 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:

func TokenValid(r *http.Request) error {
	token, err := VerifyToken(r)
	if err != nil {
		return err
	}

	if !token.Valid {
		return err
	}

	return nil
}

We will also extract the token metadata that will lookup in our Redis store we set up earlier. To extract the token, we define the ExtractTokenMetadata function:

func ExtractTokenMetadata(r *http.Request) (*AccessDetails, error) {
	token, err := VerifyToken(r)

	if err != nil {
		return nil, err
	}

	claims, ok := token.Claims.(jwt.MapClaims)

	if ok && token.Valid {
		accessUuid, ok := claims["access_uuid"].(string)

		if !ok {
			return nil, err
		}

		userId, err := strconv.ParseUint(fmt.Sprintf("%.f", claims["user_id"]), 10, 64)

		if err != nil {
			return nil, err
		}

		return &AccessDetails{
			AccessUuid: accessUuid,
			UserId:     userId,
		}, nil
	}

	return nil, err
}

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:

type AccessDetails struct {
    AccessUuid string
    UserId   uint64
}

We also mentioned looking up the token metadata in Redis. Let’s define a function that will enable us to do that:

func FetchAuth(authD *AccessDetails) (uint64, error) {
	userid, err := redisClient.Get(authD.AccessUuid).Result()

	if err != nil {
		return 0, err
	}

	userID, _ := strconv.ParseUint(userid, 10, 64)

	return userID, nil
}

FetchAuth() accepts the AccessDetails from the ExtractTokenMetadata function, then looks it up in Redis. If the record is not found, it may mean 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:

func CreateTodo(c *gin.Context) {
	var td *Todo

	if err := c.ShouldBindJSON(&td); err != nil {
		c.JSON(http.StatusUnprocessableEntity, "invalid json")
		return
	}

	tokenAuth, err := ExtractTokenMetadata(c.Request)

	if err != nil {
		c.JSON(http.StatusUnauthorized, "unauthorized")
		return
	}

	userId, err := FetchAuth(tokenAuth)

	if err != nil {
		c.JSON(http.StatusUnauthorized, "unauthorized")
		return
	}

	td.UserID = userId

	// You can proceed to save the Todo to a database
	// but we will just return it to the caller here:
	c.JSON(http.StatusCreated, td)
}

As seen, 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:

func main() {
	router := gin.Default()
	router.POST("/login", Login)
	router.POST("/todo", CreateTodo)
	log.Fatal(router.Run(":8080"))
}

To test CreateTodo, log in and copy the access_token and add it to the Authorization Bearer Token field like this:

Testing the tokens using PostmanTesting the tokens using Postman

Then add a title to the request body to create a todo and make a POST request to the /todo endpoint, as shown below:

Checking the response using PostmanChecking the response using Postman

Attempting to create a todo without an access_token will be unauthorized:

Checking an unauthorised request in PostmanChecking an unauthorised request in Postman

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 to delete a JWT metadata from Redis:

func DeleteAuth(givenUuid string) (int64, error) {
	deleted, err := redisClient.Del(givenUuid).Result()

	if err != nil {
		return 0, err
	}

	return deleted, nil
}

The function above will delete the record in redis that corresponds with the uuid passed as a parameter.

The Logout function looks like this:

func Logout(c *gin.Context) {
	au, err := ExtractTokenMetadata(c.Request)

	if err != nil {
		c.JSON(http.StatusUnauthorized, "unauthorized")
		return
	}

	deleted, delErr := DeleteAuth(au.AccessUuid)

	if delErr != nil || deleted == 0 {
		c.JSON(http.StatusUnauthorized, "unauthorized")
		return
	}

	c.JSON(http.StatusOK, "Successfully logged out")
}

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:

func main() {
	router := gin.Default()
	router.POST("/login", Login)
	router.POST("/todo", CreateTodo)
	router.POST("/logout", Logout)
	log.Fatal(router.Run(":8080"))
}

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:

Log out request using PostmanLog out request using Postman

Now the user is logged out, and no further request can be performed with that JWT again 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:

func TokenAuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		err := TokenValid(c.Request)

		if err != nil {
			c.JSON(http.StatusUnauthorized, err.Error())
			c.Abort()
			return
		}

		c.Next()
	}
}

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:

func main() {
	router := gin.Default()
	router.POST("/login", Login)
	router.POST("/todo", TokenAuthMiddleware(), CreateTodo)
	router.POST("/logout", TokenAuthMiddleware(), Logout)
	log.Fatal(router.Run(":8080"))
}

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. The user does not need to relogin. 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 like a breeze 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:

func Refresh(c *gin.Context) {
	mapToken := map[string]string{}

	if err := c.ShouldBindJSON(&mapToken); err != nil {
		c.JSON(http.StatusUnprocessableEntity, err.Error())
		return
	}

	refreshToken := mapToken["refresh_token"]

	// Verify the token
	os.Setenv("REFRESH_SECRET", "mcmvmkmsdnfsdmfdsjf")
	token, err := jwt.Parse(refreshToken, func(token *jwt.Token) (interface{}, error) {
		// Make sure that the token method conform to "SigningMethodHMAC"
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
		}

		return []byte(os.Getenv("REFRESH_SECRET")), nil
	})

	// If there is an error, the token must have expired
	if err != nil {
		c.JSON(http.StatusUnauthorized, "Refresh token expired")
		return
	}

	// Is token valid?
	if !token.Valid {
		c.JSON(http.StatusUnauthorized, err)
		return
	}

	// Since token is valid, get the uuid:
	claims, ok := token.Claims.(jwt.MapClaims) //the token claims should conform to MapClaims

	if ok && token.Valid {
		refreshUuid, ok := claims["refresh_uuid"].(string) // Convert the interface to string

		if !ok {
			c.JSON(http.StatusUnprocessableEntity, err)
			return
		}

		userId, err := strconv.ParseUint(fmt.Sprintf("%.f", claims["user_id"]), 10, 64)

		if err != nil {
			c.JSON(http.StatusUnprocessableEntity, "Error occurred")
			return
		}

		// Delete the previous Refresh Token
		deleted, delErr := DeleteAuth(refreshUuid)

		if delErr != nil || deleted == 0 { //if any goes wrong
			c.JSON(http.StatusUnauthorized, "unauthorized")
			return
		}

		// Create new pairs of refresh and access tokens
		ts, createErr := CreateToken(userId)
		if createErr != nil {
			c.JSON(http.StatusForbidden, createErr.Error())
			return
		}

		// Save the tokens metadata to redis
		saveErr := CreateAuth(userId, ts)
		if saveErr != nil {
			c.JSON(http.StatusForbidden, saveErr.Error())
			return
		}

		tokens := map[string]string{
			"access_token":  ts.AccessToken,
			"refresh_token": ts.RefreshToken,
		}
		c.JSON(http.StatusCreated, tokens)
	} else {
		c.JSON(http.StatusUnauthorized, "refresh expired")
	}
}

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, check if the token is still valid.

  • The refresh_uuid and the user_id are then extracted, which are metadata used as claims when creating the refresh token.

  • We then search for the metadata in redis store and delete it using the refresh_uuid as key.

  • We then create a new pair of access and refresh tokens that will now be used for future requests.

  • The metadata of the access and refresh tokens are saved in redis.

  • The created tokens are returned to the caller.

  • In the else statement, if the refresh token is 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:

router.POST("/token/refresh", Refresh)

Testing the endpoint with a valid refresh_token:

Testing the endpoint with a valid refresh token in PostmanTesting the endpoint with a valid refresh token in Postman

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:

var (
  VONAGE_API_KEY   = os.Getenv( "your_api_key")
  VONAGE_API_SECRET  = os.Getenv("your_secret")
)

Then, we will define some structs that have information about the sender, the receiver, and the message content:

type Todo struct {
	UserID uint64 `json:"user_id"`
	Title  string `json:"title"`
}

type Payload struct {
	From    From    `json:"from"`
	To      To      `json:"to"`
	Message Message `json:"message"`
}

type From struct {
	Type   string `json:"type"`
	Number string `json:"number"`
}

type To struct {
	Type   string `json:"type"`
	Number string `json:"number"`
}

type Content struct {
	Type string `json:"type"`
	Text string `json:"text"`
}

type Message struct {
	Content Content `json:"content"`
}

Then we define the function to send a message to a user. Make sure to add the encoding/json and bytes into your imports at the top of your main.go .

func SendMessage(username, phone string) (*http.Response, error) {
	data := Payload{
		From: From{
			Type:   "sms",
			Number: "Vonage",
		},
		To: To{
			Type:   "sms",
			Number: phone,
		},
		Message: Message{
			Content: Content{
				Type: "text",
				Text: "Dear " + username + ", a todo was created from your account just now.",
			},
		},
	}

	payloadBytes, err := json.Marshal(data)

	if err != nil {
		return nil, err
	}

	body := bytes.NewReader(payloadBytes)

	req, err := http.NewRequest("POST", "https://api.nexmo.com/v0.1/messages", body)

	if err != nil {
		return nil, err
	}

	// Ensure headers
	req.SetBasicAuth(VONAGE_API_KEY, VONAGE_API_SECRET)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")

	resp, err := http.DefaultClient.Do(req)

	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	return resp, nil
}

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 VONAGE_API_KEY and VONAGE_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:

func CreateTodo(c *gin.Context) {
	var td *Todo

	if err := c.ShouldBindJSON(&td); err != nil {
		c.JSON(http.StatusUnprocessableEntity, "invalid json")
		return
	}

	tokenAuth, err := ExtractTokenMetadata(c.Request)

	if err != nil {
		c.JSON(http.StatusUnauthorized, "unauthorized")
		return
	}

	userId, err := FetchAuth(tokenAuth)

	if err != nil {
		c.JSON(http.StatusUnauthorized, "unauthorized")
		return
	}

	td.UserID = userId

	msgResp, err := SendMessage(dummyUser.Username, dummyUser.Phone)
	
	if err != nil {
	   c.JSON(http.StatusForbidden, "error occurred sending message to user")
	   return
	}

	if msgResp.StatusCode > 299 {
	   c.JSON(http.StatusForbidden, "cannot send message to user")
	   return
	}

	// You can proceed to save the Todo to a database
	// but we will just return it to the caller here:
	c.JSON(http.StatusCreated, td)
}

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.

Share:

https://a.storyblok.com/f/270183/150x150/e744ec943a/victor-steven.png
Victor Steven

Victor Steven is a self-taught full-stack developer that loves researching about doing things differently. He has a degree in Engineering and over 5 years of experience as a software developer. Steven is very interested in designing and building scalable APIs and he enjoys writing about his discoveries in software development.