10 Jul 2022

go

JWT with Go

Author

Gurleen Sethi

JWT with Go

Getting Started #

In this article I am going to show you how to create and parse JWT tokens extracting information from them in the beautiful language of Go using the go-jwt package.

This article presumes the following:

  • You know how to program in Go.
  • You know what JWTs are and how they work (this is not an article on JWT itself 😬).

Setup #

I highly recommend following this article and trying out the code yourself. The complete example code can be found in this github repository.

Start by creating a new project using go mod init <your repository path>.

go mod init github.com/gurleensethi/go-jwt-tutorial

Adding the go-jwt package #

I will be using the go-jwt package to create and parse JWT tokens.

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

At the time of writing this article the latest version for this package is v4, check for the latest releases on the go-jwt's github repostiroy.

Creating JWT Token #

When creating JWT token's we need a secret key, so start by defining a new key.

package main

const key = "my secure jwt key" // 👈

func main() {
}
main.go

To create new tokens we use the jwt.NewWithClaims function.

package main

import (
	"fmt"
	"log"
	"github.com/golang-jwt/jwt/v4"
)

const key = "my secure jwt key"

func main() {
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{}) // 👈
    
    jwtToken, err := token.SignedString([]byte(key)) // 👈
    if err != nil {
    	log.Fatal(err)
    }
    
    fmt.Printf("JWT Token: %s\n", jwtToken)
}
main.go

Let me break this down for you:

  • NewWithClaims takes two parameters, a signing method and claims. Claims is the actual data that the JWT token will contain.
  • jwt.NewWithClaims doesn't create the new token, you need to call the SignedString function passing it the secret key to get the actual JWT token.
  • jwt.RegisteredClaims are the common/standard JWT claims that are usually present in a JWT token payload such as iat (token issue time), exp (token expiry time) and many more. I highly recommend checking our the source code for RegisteredClaims here.

Creating token with data #

The code until now creates JWT token but it doesn't contain any user provided data. It is simple, instead of providing the jwt.RegisteredClaims as the payload, you will embed jwt.RegisteredClaims in another struct with additional information, lets call it UserClaim.

type UserClaim struct {
	jwt.RegisteredClaims
	ID    int 
	Email string
	Name  string
}

Use this UserClaim as the second argument to the jwt.NewWithClaims function.

token := jwt.NewWithClaims(jwt.SigningMethodHS256, UserClaim{
	RegisteredClaims: jwt.RegisteredClaims{},
	ID: "1",
	Email: "[email protected]",
	Name: "First Last",
})

I am going to refactor the JWT token creation process into a separate function called CreateJWTToken, below is the complete code.

package main

import (
	"fmt"
	"log"
	"github.com/golang-jwt/jwt/v4"
)

const key = "my secure jwt key"

// Data that will be in the token
type UserClaim struct {
	jwt.RegisteredClaims
	ID    int 
	Email string
	Name  string
}

func main() {
	jwtToken, err := CreateJWTToken(1, "[email protected]", "First Last")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("JWT Token: %s\n", jwtToken)
}

// 👇
func CreateJWTToken(id int, email string, name string) (string, error) {
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, UserClaim{
		RegisteredClaims: jwt.RegisteredClaims{},
		ID: id,
		Email: email,
		Name: name,
	})

	// Create the actual JWT token
	signedString, err := token.SignedString([]byte(key))

	if err != nil {
		return "", fmt.Errorf("error creating signed string: %v", err)
	}

	return signedString, nil
}
main.go
Email Newsletter Icon
Don't miss new articles
Get notified once/twice per month when new articles are published.

Parsing JWT Token #

Parsing JWT tokens and extracing data from it can be done using the jwt.ParseWithClaims function (there is another function jwt.Parse which will allow you to just parse and check if token is valid).

var jwtToken string // a token generated from previous code
var userClaim UserClaim

token, err := jwt.ParseWithClaims(jwtToken, &userClaim, func(token *jwt.Token) (interface{}, error) {
	return []byte(key), nil
})

A more complete working example of parsing JWT token.

package main

import (
	"fmt"
	"log"
	"github.com/golang-jwt/jwt/v4"
)

const key = "my secure jwt key"

type UserClaim struct {
	jwt.RegisteredClaims
	ID    int 
	Email string
	Name  string
}

func main() {
	jwtToken := "a token generated from previous code"
	var userClaim UserClaim
	
    // 👇
	token, err := jwt.ParseWithClaims(jwtToken, &userClaim, func(token *jwt.Token) (interface{}, error) {
		return []byte(key), nil
	})
	if err != nil {
		log.Fatal(err)
	}

	// Checking token validity 
	if !token.Valid {
        log.Fatal("invalid token")
	}
    
    fmt.Printf("Parsed User Claim: %d %s %s\n", userClaim.ID, userClaim.Email, userClaim.Name)
}
main.go

A thing to notice here is how jwt.ParseWithClaims accepts the secret key, it takes a function as the 3rd argument in which you have to return the key.

You can check validity of the token using the Valid boolean.

If the token is valid, UserClaim will be populated with the information inside the token.

Thank you for reading this article 🙏🏻 I highly recommend going through the complete example here on GitHub.

Table of Contents
TheDeveloperCafe