JWT Authentication With Go

Pascal Allen
3 min readJul 5, 2023

This publication is a walk-through of creating, validating, and refreshing JSON Web Tokens using the HMAC signing method with Go. For simplicity, I will assume that you have already installed Go on your host machine and already have an understanding of JWTs. This publication is also based on the jwt-go library, which we will detail below.

Prerequisites

Install jwt-go

As of this writing, jwt-go is the most popular JWT library on the Go package registry. It’s maintained regularly, has great documentation, and is easy to use. You can view install instructions, as well as documentation, on their GitHub page here. Let’s begin by adding the jwt-go library to our project by running the following command in the directory that contains your go.mod file:

go get -u github.com/golang-jwt/jwt/v5

Create a Token Service

Since creating and parsing tokens is a repeated process, let’s create a file called tokenservice.go and add the following code:

package service

import (
"github.com/golang-jwt/jwt"
"os"
)

type UserClaims struct {
Id string `json:"id"`
First string `json:"first"`
Last string `json:"last"`
jwt.StandardClaims
}

func NewAccessToken(claims UserClaims) (string, error) {
accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

return accessToken.SignedString([]byte(os.Getenv("TOKEN_SECRET")))
}

func NewRefreshToken(claims jwt.StandardClaims) (string, error) {
refreshToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

return refreshToken.SignedString([]byte(os.Getenv("TOKEN_SECRET")))
}

func ParseAccessToken(accessToken string) *UserClaims {
parsedAccessToken, _ := jwt.ParseWithClaims(accessToken, &UserClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(os.Getenv("TOKEN_SECRET")), nil
})

return parsedAccessToken.Claims.(*UserClaims)
}

func ParseRefreshToken(refreshToken string) *jwt.StandardClaims {
parsedRefreshToken, _ := jwt.ParseWithClaims(refreshToken, &jwt.StandardClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(os.Getenv("TOKEN_SECRET")), nil
})

return parsedRefreshToken.Claims.(*jwt.StandardClaims)
}

The code above that you just added to your tokenservice.go file contains a struct called UserClaims that we will use as our custom JWT claims, and methods to create and parse tokens using the HMAC signing method (in this example, access tokens AND refresh tokens).

Creating JWTs

Now that we’ve created a token service, we can use it to create access and refresh tokens like so:

import (
"github.com/golang-jwt/jwt"
"<your-module>/service"
"log"
"time"
)

// user login validation should occur here

userClaims := service.UserClaims{
Id: "01H4EKGQSY5637MQP395283JR8",
First: "Leeroy",
Last: "Jenkins",
StandardClaims: jwt.StandardClaims{
IssuedAt: time.Now().Unix(),
ExpiresAt: time.Now().Add(time.Minute * 15).Unix(),
},
}

signedAccessToken, err := service.NewAccessToken(userClaims)
if err != nil {
log.Fatal("error creating access token")
}

refreshClaims := jwt.StandardClaims{
IssuedAt: time.Now().Unix(),
ExpiresAt: time.Now().Add(time.Hour * 48).Unix(),
}

signedRefreshToken, err := service.NewRefreshToken(refreshClaims)
if err != nil {
log.Fatal("error creating refresh token")
}

// do something with access, and refresh tokens.
// i.e. return them in an HTTP response for a successful login

The code snippet above is pseudocode that creates our custom JWT claims, a signed access token that expires in 15 minutes, and a signed refresh token that expires in 48 hours. This is pseudocode, but most of this can be modified and used in a real-world application — just remember to handle validation, errors, and responses appropriately.

Parsing, Validating, and Refreshing JWTs

Let’s look at how we can leverage our token service to parse, validate, and refresh JWTs that have been previously issued:

import (
"<your-module>/service"
"log"
)

var request struct {
AccessToken string `json:"access_token" binding:"required"`
RefreshToken string `json:"refresh_token" binding:"required"`
}

// request validation should occur here

userClaims := service.ParseAccessToken(request.AccessToken)
refreshClaims := service.ParseRefreshToken(request.RefreshToken)

// use parsed userClaims to validate user
// i.e. userRepository.GetById(userClaims.Id)

// refresh token is expired
if refreshClaims.Valid() != nil {
request.RefreshToken, err = service.NewRefreshToken(*refreshClaims)
if err != nil {
log.Fatal("error creating refresh token")
}
}

// access token is expired
if userClaims.StandardClaims.Valid() != nil && refreshClaims.Valid() == nil {
request.AccessToken, err = service.NewAccessToken(*userClaims)
if err != nil {
log.Fatal("error creating access token")
}
}

The code snippet above is pseudocode that handles an incoming HTTP request containing an access token and a refresh token. The jwt-go library has a function called Valid() that we can use to validate time based claims. We use this function to validate the access token and refresh token and refresh each token if they are expired. Again, this is pseudocode, but most of this can be modified and used in a real-world application — just remember to modify it to your needs.

Conclusion

This tutorial is meant to be a very basic foundation to what’s possible with JWT authentication and Go. I hope you found this to be informative. If you’re not familiar with JWTs, you can visit their website here for interactive documentation. Thanks and have a great day!

--

--