Implementing JWT Token Authentication in Golang

Cheick Zida
5 min readAug 1, 2023

--

Introduction

JSON Web Tokens (JWTs) have gained significant popularity as a secure and efficient method for implementing authentication and authorization mechanisms in web applications. In this article, we will explore how to implement JWT token authentication in Golang using the golang-jwt/jwt package and build a login system to secure protected routes. The full code is available on my Github

Prerequisites

To follow along with this tutorial, you should have a basic understanding of Go programming and have Go installed on your machine. Additionally, you should have a text editor or an integrated development environment (IDE) for writing the Go code.

Installing the Package

Before we start implementing JWT token authentication, we need to install the golang-jwt/jwt package. Open your terminal or command prompt and execute the following command:

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

This will download and install the package, making it available for use in our application.

Creating JWT Tokens

The first step in implementing JWT token authentication is to create and sign tokens. Let’s create a function that generates a new JWT token:

package main

import (
"fmt"
"github.com/golang-jwt/jwt/v5"
"time"
)

var secretKey = []byte("secret-key")

func createToken(username string) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256,
jwt.MapClaims{
"username": username,
"exp": time.Now().Add(time.Hour * 24).Unix(),
})

tokenString, err := token.SignedString(secretKey)
if err != nil {
return "", err
}

return tokenString, nil
}

In the above code snippet, we import the necessary packages, including github.com/golang-jwt/jwt/v5. We create a new JWT token using the jwt.NewWithClaims() function. We specify the signing method as HS256 and relevant informations such as the username and the token expiration time. Then, we sign the token with a secret key and return the generated token as a string.

Verifying JWT Tokens

Once we have a token, we need to verify its authenticity before granting access to protected resources. Let’s create a function that verifies and parses a JWT token:

func verifyToken(tokenString string) error {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return secretKey, nil
})

if err != nil {
return err
}

if !token.Valid {
return fmt.Errorf("invalid token")
}

return nil
}

In the above code snippet, we use the jwt.Parse() function to parse and verify the token. We provide a callback function to retrieve the secret key used for signing the token. If the token is valid, we continue processing the request; otherwise, we return an error indicating that the token is invalid.

Implementing a Login System

To secure protected routes, we need to introduce a login system where users can authenticate and obtain a JWT token. Let’s create a login endpoint where users can provide their username and password:

func LoginHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")

var u User
json.NewDecoder(r.Body).Decode(&u)
fmt.Printf("The user request value %v", u)

if u.Username == "Chek" && u.Password == "123456" {
tokenString, err := CreateToken(u.Username)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Errorf("No username found")
}
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, tokenString)
return
} else {
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprint(w, "Invalid credentials")
}
}

In the above code snippet, we define a /login endpoint that accepts a POST request with username and password in the request body. We simulate user authentication by checking if the provided credentials match the expected values. If the credentials are valid, we generate a JWT token using the createToken() function and return it as the response. Otherwise, we return an appropriate error response.

Securing Protected Routes

Now that we have implemented the login system and obtained a JWT token, let’s modify the protected route to require a valid JWT token for access:

func ProtectedHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
tokenString := r.Header.Get("Authorization")
if tokenString == "" {
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprint(w, "Missing authorization header")
return
}
tokenString = tokenString[len("Bearer "):]

err := verifyToken(tokenString)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprint(w, "Invalid token")
return
}

fmt.Fprint(w, "Welcome to the the protected area")

}

In the above code snippet, we check for a valid JWT token in the Authorization header of the request. If the token is missing or invalid, we return an appropriate error response. Otherwise, we proceed with serving the protected resource.

Here is the full code (Github repo)

package main

import (
"fmt"
"github.com/gorilla/mux"
"github.com/golang-jwt/jwt"
"net/http"
"encoding/json"
"time"
)

var secretKey = []byte("secret-key")

func main() {
router := mux.NewRouter()

router.HandleFunc("/login", login.LoginHandler).Methods("POST")
router.HandleFunc("/protected", login.ProtectedHandler).Methods("GET")

fmt.Println("Starting the server")
err := http.ListenAndServe("localhost:4000", router)
if err != nil {
fmt.Println("Could not start the server", err)
}
}

func LoginHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")

var u User
json.NewDecoder(r.Body).Decode(&u)
fmt.Printf("The user request value %v", u)

if u.Username == "Chek" && u.Password == "123456" {
tokenString, err := CreateToken(u.Username)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Errorf("No username found")
}
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, tokenString)
return
} else {
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprint(w, "Invalid credentials")
}
}

func ProtectedHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
tokenString := r.Header.Get("Authorization")
if tokenString == "" {
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprint(w, "Missing authorization header")
return
}
tokenString = tokenString[len("Bearer "):]

err := verifyToken(tokenString)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprint(w, "Invalid token")
return
}

fmt.Fprint(w, "Welcome to the the protected area")

}

func createToken(username string) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256,
jwt.MapClaims{
"username": username,
"exp": time.Now().Add(time.Hour * 24).Unix(),
})

tokenString, err := token.SignedString(secretKey)
if err != nil {
return "", err
}

return tokenString, nil
}

func verifyToken(tokenString string) error {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return secretKey, nil
})

if err != nil {
return err
}

if !token.Valid {
return fmt.Errorf("invalid token")
}

return nil
}

Note that we additionally, created a new HTTP router using http.NewRouter(). We then defined the route handlers for /login and /protected endpoints using router.HandleFunc(). Finally, we started the server by calling http.ListenAndServe(":4000", router).

This setup allows the /login endpoint to handle the login process and generate a JWT token, while the /protected endpoint verifies the JWT token before granting access to the protected area.

We can test our application with Postman, by sending a Post request to the login endpoint http://localhost:4000/login, with our username and password as request body.

Post request with

The response body returns a JWT token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTA4NzA2MjksInVzZXJuYW1lIjoiQ2hlayJ9.oPHtaTaIKwSuetkYpVwLMkKnCI-c9aGEKS5vFDBFl2Y.

We now send a GET request, with the above JWT token set in the Authorization header, tohttp://localhost:4000/protected to access the protected area.

Conclusion

In this article, we explored how to implement JWT token authentication in Golang. We learned how to create and sign JWT tokens, verify their authenticity, and build a login system to secure protected routes. By integrating JWT token authentication into your Go applications, you can enhance security and implement stateless authentication mechanisms.
The source code is available on my Github .

--

--

Cheick Zida

Software engineer | backend | blockchain | Web3 | Golang | Node.js | Solidity || Linkedin: https://www.linkedin.com/in/chek-zida-555301ab/