Golang JWT (Json Web Token) Examples

Introduction to Golang JWT

In the previous chapter, we built a simple HTTP client and server using Golang. Today, we will discuss what is JSON Web Token and how to use it in Golang with go-jwt package. Later in this tutorial, you'll learn how to apply authentication to our handler. In this example, we will create 2 endpoints: /login endpoint (which only accepts the POST method), the user has to send the username and password. If the user is authenticated, the server will send back JWT to the client. Only the request with jwt token can access the endpoint /getAllBook.

JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code
(MAC) and/or encrypted.

Advertisement

In short, it's a signed JSON object that does something useful (for example, authentication). It's commonly used for Bearer tokens in Oauth 2. A token is made of three parts, separated by .'s. The first two parts are JSON objects, that have been base64url encoded. The last part is the signature, encoded the same way.

jwt-go: An implementation of JSON Web Tokens.

 

Installation guide and version support

Support for Go versions is consistent with the way Go releases new versions. So long as there are two more recent major releases, they will support a major version of Go. Since unsupported Go versions contain security flaws that won't be patched, they no longer allow constructing JWT-GO with them.

Run the command below to add jwt-go as a dependency in Go program:

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

Now we have to import to our program before use it:

import "github.com/golang-jwt/jwt/v4"

 

Setting up an HTTP server with golang JWT

Build a simple HTTP server

First, we have to build a simple HTTP server that listen on port: 8080 (you can use any port of your choice):

Advertisement
package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "This is GolinuxCloud's http server")
	})
	http.ListenAndServe(":8080", nil)
}

Output:

Golang JWT (Json Web Token) Examples

 

Build the authentication endpoint

Our HTTP server is not authenticated at this time, thus requests to the page can be made without restriction. In the below example, we will learn how to authenticate users. We store all users in a map, and User struct to parse the body from the request:

package main

import (
	"net/http"
	"encoding/json"
	"fmt"
)

// store all user 
var userMap = map[string]string{
	"golinuxcloud" : "testpw",
	"testUser1": "testUser1",
	"testUser2": "testUser2",
}


// USer type
type User struct {
	Username string	`json:"username"`
	Password string	`json:"password"`
}

func main() {
	http.HandleFunc("/login", loginHandler)
	http.ListenAndServe(":8080", nil)
}


func loginHandler(writer http.ResponseWriter, request *http.Request) {
    switch request.Method {
	case "POST":
		var user User

		// decode the request body into the struct, If error, respond to the client with the error message and a 400 status code.
		err := json.NewDecoder(request.Body).Decode(&user)
		if err != nil {
			fmt.Println(user)
			fmt.Fprintf(writer, "invalid body")
		}
		
		if (userMap[user.Username] == "" || userMap[user.Username] != user.Password) {
			fmt.Fprintf(writer, "can not authenticate this user")
		}


	case "GET":
		fmt.Fprintf(writer, "only POST methods is allowed.")
	}
}

 

Generating JWTs for authentication using the Golang-JWT package

The JWT package's Ne() function allows you to create new tokens. The JWT token is returned by the New function after accepting a signing method (the JWT's cryptographic algorithm). We can use the Claims method to modify the token.

In this example, you're utilizing the time module along with the username to set the JWT's one-minute expiration time. When attempting to verify the JWT, you'll be able to retrieve the claims.

The string must be signed using your private key as the last step in creating a JWT. Utilizing the SignedString method of the token, you may sign your token string. A signed token string is produced using the SignedString method using the secret key.

In the below example, we will learn how to generate a JWT token:

var sampleSecretKey = []byte("GoLinuxCloudKey")

func generateJWT(username string) (string, error) {
	token := jwt.New(jwt.SigningMethodHS256)
	claims := token.Claims.(jwt.MapClaims)

	claims["authorized"] = true
	claims["username"] = username
	claims["exp"] = time.Now().Add(time.Minute * 30).Unix()

	tokenString, err := token.SignedString(sampleSecretKey)

	if err != nil {
		fmt.Errorf("Something Went Wrong: %s", err.Error())
		return "", err
	}
	return tokenString, nil
}

Output:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkIjp0cnVlLCJleHAiOjE2NjczNjM2MDQsInVzZXJuYW1lIjoidGVzdFVzZXIxIn0.915CaKQ-ZDhWAt7P_SE168UdDSMnvKbbXxGatns4aog

 

Verifying JWT tokens

For the /getAllBooks endpoint, we want to check if the user is authenticated. First, we will check if the request header contains the Token field, then check if we can parse the token.

The Parse method of the jwt package can be used to parse the token, which you must do. The token and a JWT decorator function are passed into the parse method, which then returns an interface and an error.

func validateToken(w http.ResponseWriter, r *http.Request) (err error) {

	if r.Header["Token"] == nil {
		fmt.Fprintf(w, "can not find token in header")
		return
	}

	token, err := jwt.Parse(r.Header["Token"][0], func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("There was an error in parsing")
		}
		return sampleSecretKey, nil
	})

	if token == nil {
		fmt.Fprintf(w, "invalid token")
	}

	return nil
}

 

Extracting claims from JWT tokens

In the code shown above, we haven't checked the expired time for that token. If we want to extract this, we need to extract the claim from the token. Here’s how you can extract the claims, using the exp claims as an example:

	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok {
		fmt.Fprintf(w, "couldn't parse claims")
		return errors.New("Token error")
	}

	exp := claims["exp"].(float64)
	if int64(exp) < time.Now().Local().Unix() {
		fmt.Fprintf(w, "token expired")
		return errors.New("Token error")
	}

 

Calling the /getAllBooks endpoint

After verifying the token, now we can access the /getAllBooks endpoint as shown below. We store all books in a slice. We will use json package to marshall the data and noted that you have to set the "Content-type" in the response writer's to "application/json" to return the JSON data:

func getAllBookHandler(w http.ResponseWriter, r *http.Request) {
	err := validateToken(w, r)
	if err == nil {
		w.Header().Set("Content-Type", "application/json")
		books := getAllBook()
		json.NewEncoder(w).Encode(books)
	}
}

func getAllBook() []Book {
	return []Book{
		Book{
			Name:   "Book1",
			Author: "Author1",
		},
		Book{
			Name:   "Book2",
			Author: "Author2",
		},
		Book{
			Name:   "Book3",
			Author: "Author3",
		},
	}
}

type Book struct {
	Name   string
	Author string
}

 

Full Server Code

package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"time"

	"github.com/golang-jwt/jwt"
)

// store all user
var userMap = map[string]string{
	"golinuxcloud": "testpw",
	"testUser1":    "testUser1",
	"testUser2":    "testUser2",
}

// USer type
type User struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

func main() {
	http.HandleFunc("/login", loginHandler)
	http.HandleFunc("/getAllBooks", getAllBookHandler)
	http.ListenAndServe(":8080", nil)
}

func loginHandler(writer http.ResponseWriter, request *http.Request) {
	switch request.Method {
	case "POST":
		var user User

		// decode the request body into the struct, If error, respond to the client with the error message and a 400 status code.
		// fmt.Println(request.Body)
		// err := json.NewDecoder(request.Body).Decode(&user)
		err := json.NewDecoder(request.Body).Decode(&user)
		if err != nil {
			fmt.Fprintf(writer, "invalid body")
			return
		}

		if userMap[user.Username] == "" || userMap[user.Username] != user.Password {
			fmt.Fprintf(writer, "can not authenticate this user")
			return
		}

		token, err := generateJWT(user.Username)
		if err != nil {
			fmt.Fprintf(writer, "error in generating token")
		}

		fmt.Fprintf(writer, token)

	case "GET":
		fmt.Fprintf(writer, "only POST methods is allowed.")
		return
	}
}

var sampleSecretKey = []byte("GoLinuxCloudKey")

func generateJWT(username string) (string, error) {
	token := jwt.New(jwt.SigningMethodHS256)
	claims := token.Claims.(jwt.MapClaims)

	claims["authorized"] = true
	claims["username"] = username
	claims["exp"] = time.Now().Add(time.Minute * 1).Unix()

	tokenString, err := token.SignedString(sampleSecretKey)

	if err != nil {
		fmt.Errorf("Something Went Wrong: %s", err.Error())
		return "", err
	}
	return tokenString, nil
}

func validateToken(w http.ResponseWriter, r *http.Request) (err error) {
	if r.Header["Token"] == nil {
		fmt.Fprintf(w, "can not find token in header")
		return errors.New("Token error")
	}

	token, err := jwt.Parse(r.Header["Token"][0], func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("There was an error in parsing")
		}
		return sampleSecretKey, nil
	})

	if token == nil {
		fmt.Fprintf(w, "invalid token")
		return errors.New("Token error")
	}

	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok {
		fmt.Fprintf(w, "couldn't parse claims")
		return errors.New("Token error")
	}

	exp := claims["exp"].(float64)
	if int64(exp) < time.Now().Local().Unix() {
		fmt.Fprintf(w, "token expired")
		return errors.New("Token error")
	}

	return nil
}

func getAllBookHandler(w http.ResponseWriter, r *http.Request) {
	err := validateToken(w, r)
	if err == nil {
		w.Header().Set("Content-Type", "application/json")
		books := getAllBook()
		json.NewEncoder(w).Encode(books)
	}
}

func getAllBook() []Book {
	return []Book{
		Book{
			Name:   "Book1",
			Author: "Author1",
		},
		Book{
			Name:   "Book2",
			Author: "Author2",
		},
		Book{
			Name:   "Book3",
			Author: "Author3",
		},
	}
}

type Book struct {
	Name   string
	Author string
}

 

Setting up an HTTP client with golang JWT

Request to login

Here is an example of a request to log in, we can receive the jwt token after authenticated:

Advertisement
import (
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
)

func main() {
        // define the URL and method
	url := "http://localhost:8080/login"
	method := "POST"

	payload := strings.NewReader(`{
    "username": "testUser1",
    "password": "testUser1"
}`)

	client := &http.Client{}
	req, err := http.NewRequest(method, url, payload)

	if err != nil {
		fmt.Println(err)
		return
	}
	req.Header.Add("Content-Type", "application/json")

	res, err := client.Do(req)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer res.Body.Close()

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(string(body))
}

Output:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkIjp0cnVlLCJleHAiOjE2NjczNjY5MjgsInVzZXJuYW1lIjoidGVzdFVzZXIxIn0.mMs17_cptuw59yXQkpVYP21xkeI4-AM6NbhIzNRLIdw

If an invalid username and password, the output will be:

can not authenticate this user

 

Request to query all books

Now we can add the Token to the request's header and get information of all books:

	// get the token
	token := string(body)

	url2 := "http://localhost:8080/getAllBooks"
	method2 := "GET"

	req2, err := http.NewRequest(method2, url2, nil)

	if err != nil {
		fmt.Println(err)
		return

	}
	req2.Header.Add("Token", token)

	res2, err := client.Do(req2)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer res2.Body.Close()

	body2, err := ioutil.ReadAll(res2.Body)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println(string(body2))

Output:

[{"Name":"Book1","Author":"Author1"},{"Name":"Book2","Author":"Author2"},{"Name":"Book3","Author":"Author3"}]

If we not set the Token to header, the output will be:

url2 := "http://localhost:8080/getAllBooks"
	method2 := "GET"

	req2, err := http.NewRequest(method2, url2, nil)

	if err != nil {
		fmt.Println(err)
		return

	}
	// req2.Header.Add("Token", token)

	res2, err := client.Do(req2)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer res2.Body.Close()

	body2, err := ioutil.ReadAll(res2.Body)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println(string(body2))

Output:

Advertisement
can not find token in header

 

Verify jwt token

Now we will try to set a random token to /getAllBooks request:

	url2 := "http://localhost:8080/getAllBooks"
	method2 := "GET"

	req2, err := http.NewRequest(method2, url2, nil)

	if err != nil {
		fmt.Println(err)
		return

	}
        // set a random token 
	req2.Header.Add("Token", "SomeRandomString")

	res2, err := client.Do(req2)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer res2.Body.Close()

	body2, err := ioutil.ReadAll(res2.Body)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println(string(body2))

Output:

invalid token

 

Expired token

Here is an example of sending an expired token to the server. Server side, the expiration time for every token is 1 minute, in this example, we will wait 2 minutes and then send that token in the request:

token := string(body)
	time.Sleep(time.Minute * 2)
	url2 := "http://localhost:8080/getAllBooks"
	method2 := "GET"

	req2, err := http.NewRequest(method2, url2, nil)

	if err != nil {
		fmt.Println(err)
		return

	}
	req2.Header.Add("Token", token)

	res2, err := client.Do(req2)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer res2.Body.Close()

	body2, err := ioutil.ReadAll(res2.Body)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println(string(body2))

Output:

token expired

 

Summary

This tutorial demonstrated how to use the golang-jwt package to authenticate the endpoints of your Go API and web page using JSON Web Tokens. The tutorial's whole source code is available as a reference.

Don't forget to use environment variables for your secret keys and avoid encrypting private information using JWTs. To get started with the language or framework you're interested in using, check out the various tutorials on the GoLinuxCloud blog!

Advertisement

 

References

https://en.wikipedia.org/wiki/JSON_Web_Token
https://datatracker.ietf.org/doc/html/rfc7519
https://pkg.go.dev/github.com/golang-jwt/jwt

 

Categories GO

Didn't find what you were looking for? Perform a quick search across GoLinuxCloud

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can either use the comments section or contact me form.

Thank You for your support!!

Leave a Comment

X