Implementing JWT Authentication in a Golang Application
Tags: webdev, tutorial, authentication, go
Published: 2024-01-30 13:27:45


In this tutorial, we'll explore the fundamentals of JWT authentication, understanding its significance, and then transition into a hands-on implementation. 

Throughout this guide, we'll cover:
- A brief overview of JWT and its structure
- Creating a Simple ToDo Application with Go
- Examining the Golang-JWT package
- Creating JWT Tokens and Adding Claims using Golang-JWT
- Signing and Verifying JWTs

You can find the implementation source code in the following [GitHub repository](https://github.com/Imranalam28/jwt-authentication-in-go-app) 

A quick note before we start: We leave out some aspects that an actual application might need in terms of session management. We aim to keep things simple and focus on providing a straightforward approach to help you get started with JWT authentication in your Golang applications.

## **What is JWT**

[JWTs, or JSON Web Tokens](https://jwt.io/), serve as compact and self-contained data structures for transmitting information securely between parties. JWTs specify the token type, contain claims about an entity, and ensure integrity through cryptographic signatures. 

### **Structure of JWT**

JWTs consist of three parts: the header, the payload, and the signature.

1. **Header**

The header typically consists of two parts: the type of the token, which is JWT, and the signing algorithm being used, such as HMAC SHA256 or RSA.

Example:

```json
{
  "alg": "HS256",
  "typ": "JWT"
}

```

2. **Payload**

The payload contains claims. Claims are statements about an entity (typically, the user) and additional data. There are three types of claims: registered, public, and private claims. Registered claims include standard fields like issuer (**`iss`**), subject (**`sub`**), audience (**`aud`**), expiration time (**`exp`**), and issued at (**`iat`**).

Example:

```json
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}
```

3. **Signature**

The signature is created by combining the encoded header, encoded payload, a secret, and the specified signing algorithm. It ensures the integrity and authenticity of the token.

Example (using HMAC SHA256):

```scss
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)
```

Now, we're ready to implement JWT to enhance the security of our application.

## Creating a Simple ToDo Application

For the purpose of demonstrating JWT authentication, we will create a simple ToDo list application with [Role-Based Access Control (RBAC)](https://www.upguard.com/blog/rbac). We will introduce two distinct roles: **senior** and **employee**. These roles will serve as the basis for managing user access and permissions within our application.

- **Senior:** Senior users have elevated privileges and can perform actions such as adding ToDo items to the list.
- **Employee:** Employees have more restricted access and cannot add new ToDo items.

Roles are typically assigned during user authentication and embedded in the JWT claims.

Now, let's dive into the fun part – creating our basic ToDo application using the powerful [Gin framework](https://gin-gonic.com/). This section will walk you through the steps, breaking down the code into manageable snippets.

### Step 1: Project Setup

Before we start, ensure you have Go installed and then initialize a new Go module and fetch the Gin package:

```bash
go mod init go-to-do
go get -u github.com/gin-gonic/gin
```

This sets up a new Go module and grabs the necessary Gin dependencies.

### Step 2: Project Structure

Now, let's structure our project. Create a main Go file, `main.go`, and a `templates` folder to hold our HTML files:

```json
go-to-do
   - main.go
   - templates
       - index.html
```

In `main.go`, start by importing necessary packages and defining our `Todo` struct:

```go
package main

import (
	"net/http"
	"strconv"
	"github.com/gin-gonic/gin"
)

type Todo struct {
	Text string
	Done bool
}

var todos []Todo
var loggedInUser string
```

Here, we set up our basic ToDo structure and create a global variable for storing ToDo items (`todos`) and the currently logged-in user (`loggedInUser`).

### ****Step 3: Initialize Gin Router and Define Main Route****

Now, initialize our Gin router, set up static file serving, and define the main route to render the ToDo list:

```go
// Add routes for the ToDo App
func main() {
	router := gin.Default()

	router.Static("/static", "./static")
	router.LoadHTMLGlob("templates/*")

	router.GET("/", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.html", gin.H{
			"Todos":    todos,
			"LoggedIn": loggedInUser != "",
			"Username": loggedInUser,
		})
	})

	router.POST("/add", func(c *gin.Context) {
		text := c.PostForm("todo")
		todo := Todo{Text: text, Done: false}
		todos = append(todos, todo)
		c.Redirect(http.StatusSeeOther, "/")
	})

	router.POST("/toggle", func(c *gin.Context) {
		index := c.PostForm("index")
		toggleIndex(index)
		c.Redirect(http.StatusSeeOther, "/")
	})

	router.Run(":8080")
}

func toggleIndex(index string) {
	i, _ := strconv.Atoi(index)
	if i >= 0 && i < len(todos) {
		todos[i].Done = !todos[i].Done
	}
}
```

Here, we create a Gin router, configure it to serve static files and load HTML templates. The main route (`/`) renders the `index.html` template, passing along ToDo items, login status, and the username. Additionally, we handle the addition of new ToDo items and toggling their completion status.

### ****Step 4: Create HTML Template****

Create the `index.html` file in the `templates` folder:

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="/static/style.css">
    <title>ToDo List</title>
</head>
<body>
    <h1>Welcome, User</h1>

    <!-- Form for adding new ToDo items -->
    <form action="/add" method="post">
        <input type="text" name="todo" required>
        <button type="submit">Add ToDo</button>
    </form>

    <!-- ToDo list -->
    <ul>
        {{ range $index, $todo := .Todos }}
            <li>
                <form action="/toggle" method="post" style="display:inline;">
                    <input type="hidden" name="index" value="{{ $index }}">
                    <input type="checkbox" {{ if $todo.Done }}checked{{ end }} onchange="this.form.submit()">
                </form>
                {{ if $todo.Done }}
                    <del>{{ $todo.Text }}</del>
                {{ else }}
                    {{ $todo.Text }}
                {{ end }}
            </li>
        {{ end }}
    </ul>

</body>
</html>
```

This HTML template includes a form for adding new ToDo items and a list to display existing items. The template uses the Go template syntax to loop through the `Todos` slice and dynamically render each item.

That's it for now! You've set up the basic structure and added the functionality to display and add ToDo items. Run the application, and you should be able to see your ToDo list in action at **`http://localhost:8080`**.

![simple todo](https://github.com/Permify/permify/assets/34595361/93bee0a8-fb71-47c5-aefa-4a3abd5d897e)

Now, with these additions, you have the basic functionality of adding new ToDo items and toggling their completion status. Your ToDo application is ready for further enhancements!

## Getting Started With Golang-jwt

To add JWT authentication to our ToDo application, we'll be using [the Golang-jwt library](https://github.com/golang-jwt/jwt). The **`golang-jwt`** package simplifies the implementation of JWTs in Go applications, offering a suite of convenient functions that abstract away the complexities associated with token creation, verification, and management.

To incorporate Golang-jwt into our Go project, we can easily install it using the following command:

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

This command fetches the necessary dependencies and makes Golang-jwt readily available for integration into our ToDo application. Now we are ready to dive into the creation of JWT tokens and the addition of claims to represent user information and roles.

## Creating JWT Tokens and Adding Claims

Now, let's enhance our ToDo application by adding JWT authentication. In this section, we'll focus on creating JWT tokens and adding claims to represent user information and roles.

### ****Step 5: Set Up Secret Key****

To get started with JWT token creation, we'll first import the required packages and set up a secret key. The secret key is crucial for both signing and verifying JWTs. Here's the code:

```go
// Import the required packages
import (
	"fmt"
	"time"
	"net/http"
	"strconv"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
)
// Add a new global variable for the secret key
var secretKey = []byte("your-secret-key")

// Function to create JWT tokens with claims
func createToken(username string) (string, error) {
	// ... (Code for creating JWT tokens and adding claims will go here)
}
```

In this part, we import the necessary packages for JWT and Gin. We also create a global variable **`secretKey`**, which represents our [secret cryptographic key](https://www.cloudflare.com/learning/ssl/what-is-a-cryptographic-key/) used for token signing and verification.

### ****Step 6: Create Token with Claims****

Now, let's proceed with the code responsible for creating JWT tokens and adding claims:

```go
// Function to create JWT tokens with claims
func createToken(username string) (string, error) {
    // Create a new JWT token with claims
	claims := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"sub": username,                    // Subject (user identifier)
		"iss": "todo-app",                  // Issuer
		"aud": getRole(username),           // Audience (user role)
		"exp": time.Now().Add(time.Hour).Unix(), // Expiration time
		"iat": time.Now().Unix(),                 // Issued at
	})

  // Print information about the created token
	fmt.Printf("Token claims added: %+v\n", claims)
	return tokenString, nil
}
```

Here, we've defined the **`createToken`** function. It takes a **`username`** as input and generates a JWT token with specific claims such as subject (**`sub`**), issuer (**`iss`**), audience (**`aud`**), expiration time (**`exp`**), and issued at (**`iat`**). 

Now, let's add the **`getRole`** function, which helps determine the role of a user based on their username:

```go
func getRole(username string) string {
	if username == "senior" {
		return "senior"
	}
	return "employee"
}
```

This simple function checks if the username is **senior** and returns the role **senior**, otherwise, it defaults to **employee**. It will be used in the **`createToken`** function to set the audience claim of the JWT.

With these three parts, you've set up the foundation for creating JWT tokens with claims in your application. The  **`createToken`** function can be utilized to generate tokens with specific user information.

Once tokens are created, the next step is to sign them when they are presented during authentication.

## Signing JWT Tokens

To ensure the integrity and authenticity of the token, we need to sign it using our secret key. The signing process is handled internally by the **`jwt`** package. Here's the code:

```go
// Sign the previously created token
func createToken(username string) (string, error) {
    // ... (previous code)

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

    // ... (previous code)
}
```

In this part, we've included the code to sign the JWT token with our secret key after adding claims. The signed token is then returned.

### ****Step 7: Update Login Route to Include Token Creation****

Now, let's modify the **`/login`** route in the **`main.go`** file to include token creation and sending:

```go
// Create route for user authentication
router.POST("/login", func(c *gin.Context) {
		username := c.PostForm("username")
		password := c.PostForm("password")

		// Dummy credential check
		if (username == "employee" && password == "password") || (username == "senior" && password == "password") {
			tokenString, err := createToken(username)
			if err != nil {
				c.String(http.StatusInternalServerError, "Error creating token")
				return
			}

			loggedInUser = username
			fmt.Printf("Token created: %s\n", tokenString)
			c.SetCookie("token", tokenString, 3600, "/", "localhost", false, true)
			c.Redirect(http.StatusSeeOther, "/")
		} else {
			c.String(http.StatusUnauthorized, "Invalid credentials")
		}
})
```

Here, we've updated the **`/login`** route to include the **`createToken`** function. After validating the username and password (in this case, using dummy credentials), if successful, we create a JWT token and set it as a cookie in the response.

### ****Step 8: Update HTML Template for Login Form****

Finally, update the login form in the **`index.html`** file to include the username and password fields:

```html
{{ if not .LoggedIn }}
<h1>Login</h1>
<!-- Login form -->
<form action="/login" method="post">
    <input type="text" name="username" placeholder="Username" required>
    <input type="password" name="password" placeholder="Password" required>
    <button type="submit">Login</button>
</form>
{{ else }}
<!-- ... (previous code) -->
{{ end }}

```

This modification includes the fields for entering a username and password in the login form. This information will be sent to the server for authentication. 

![token creation](https://github.com/Permify/permify/assets/34595361/c06a5186-5e38-4114-b796-8e7734a3dab9)

In the next section, we'll explore token verification to ensure secure authentication.

## Verifying JWT Tokens

In this section, we'll implement a middleware function to verify incoming JWT tokens. This verification step ensures the integrity and authenticity of the tokens before processing requests that require authentication.

### Step 9: Add Middleware for Token Verification

Firstly, let's create a middleware function for verifying JWT tokens:

```go
// Function to verify JWT tokens
func authenticateMiddleware(c *gin.Context) {
	// Retrieve the token from the cookie
	tokenString, err := c.Cookie("token")
	if err != nil {
		fmt.Println("Token missing in cookie")
		c.Redirect(http.StatusSeeOther, "/login")
		c.Abort()
		return
	}

	// Verify the token
	token, err := verifyToken(tokenString)
	if err != nil {
		fmt.Printf("Token verification failed: %v\\n", err)
		c.Redirect(http.StatusSeeOther, "/login")
		c.Abort()
		return
	}

	// Print information about the verified token
	fmt.Printf("Token verified successfully. Claims: %+v\\n", token.Claims)

	// Continue with the next middleware or route handler
	c.Next()
}
```

Here, the **`authenticateMiddleware`** function retrieves the JWT token from the cookie, attempts to verify it using the **`verifyToken`** function, and redirects to the login page if the token is missing or verification fails. 

If the token is successfully verified, information about the token's claims is printed, and the middleware allows the request to continue to the next handler.

### Step 10: Add Middleware to Protected Routes

Next, let's apply the **`authenticateMiddleware`** to the routes that require authentication, such as adding and toggling ToDo items:

```go
// Apply middleware to routes that require authentication
router.POST("/add", authenticateMiddleware, func(c *gin.Context) {
	// ... (previous code)
})

router.POST("/toggle", authenticateMiddleware, func(c *gin.Context) {
	// ... (previous code)
})
```

By applying the middleware to these routes, we ensure that requests to add or toggle ToDo items will only be processed if the incoming token is successfully verified.

### Step 11: Verify Token

Now, let's define the **`verifyToken`** function for actual token verification:

```go
// Function to verify JWT tokens
func verifyToken(tokenString string) (*jwt.Token, error) {
	// Parse the token with the secret key
	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		return secretKey, nil
	})

	// Check for verification errors
	if err != nil {
		return nil, err
	}

	// Check if the token is valid
	if !token.Valid {
		return nil, fmt.Errorf("invalid token")
	}

	// Return the verified token
	return token, nil
}
```

This function parses the JWT token using the **`jwt.Parse`** method, with our secret key used for verification. It checks for verification errors and token validity. If the token is valid, it returns the verified token.

![token verification](https://github.com/Permify/permify/assets/34595361/89a66ad9-0f9b-471d-a6eb-67ef5a7408f5)

With these modifications, you've successfully implemented token verification middleware and applied it to routes that require authentication in your ToDo application. Now you can add role-based permissions to enhance the security and functionality of your application.

## Role-Based Permissions

In this section, we'll introduce [Role-Based Access Control (RBAC)](https://www.permify.co/post/rbac-vs-rebac-when-to-use-them/) to our ToDo application, enhancing user interactions based on assigned roles. Our ToDo application will now have two distinct roles: **senior** and **employee**. 

Seniors will enjoy elevated privileges, including the ability to add new ToDo items to the list. On the other hand, employees will have more restricted access and won't be able to add new ToDo items. 

This implementation ensures a tailored user experience, with specific permissions assigned to each role, making our application more versatile and secure.

Update the **`main.go`** file to include the necessary changes for role-based permissions:

```go
router.GET("/", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.html", gin.H{
			"Todos":    todos,
			"LoggedIn": loggedInUser != "",
			"Username": loggedInUser,
			"Role":     getRole(loggedInUser),
		})
	})

router.GET("/logout", func(c *gin.Context) {
	loggedInUser = ""
	c.SetCookie("token", "", -1, "/", "localhost", false, true)
	c.Redirect(http.StatusSeeOther, "/")
	})
```

In the above code, we've updated the main route (**/**) to include the user's role in the HTML template data. Additionally, we've added a new route (**/logout**) for logging out, which clears the logged-in user and redirects to the main page.

Update the `index.html` file to conditionally display the form for adding ToDo items based on the user's role:

```html
{{ if .LoggedIn }}
<h1>Welcome, {{ .Username }}</h1>
<a href="/logout">Logout</a>

{{ if eq .Role "senior" }}
<!-- Form for seniors to add new ToDo items -->
<form action="/add" method="post">
    <input type="text" name="todo" required>
    <button type="submit">Add ToDo</button>
</form>
{{ else }}
<p>Employees can't add todos.</p>
{{ end }}

<!-- ToDo list -->
<ul>
        {{ range $index, $todo := .Todos }}
            <li>
                <form action="/toggle" method="post" style="display:inline;">
                    <input type="hidden" name="index" value="{{ $index }}">
                    <input type="checkbox" {{ if $todo.Done }}checked{{ end }} onchange="this.form.submit()">
                </form>
                {{ if $todo.Done }}
                    <del>{{ $todo.Text }}</del>
                {{ else }}
                    {{ $todo.Text }}
                {{ end }}
            </li>
        {{ end }}
    </ul>

{{ end }}
```

Now, seniors will see the form, while employees will see a message indicating that they can't add ToDo items. The user's role is retrieved from the data provided by the Golang code.

![role based permissions](https://github.com/Permify/permify/assets/34595361/323272b7-6482-4c99-b305-287b0cd5dc4e)

With these updates, the ToDo application enforces role-based permissions, allowing only seniors to add new ToDo items.

## Closing

We've successfully implemented JWT authentication in a Golang ToDo application. We covered the basics of JWT, role-based authorization, token creation, signing and verification. 

Understanding these concepts is crucial for building secure and scalable applications with user authentication. 

As you continue to refine your application's security and scalability, you'll find that managing granular permissions becomes increasingly challenging. 

At that point consider exploring our solution, [Permify](https://github.com/Permify/permify). It's a Google Zanzibar-based open-source authorization service that helps to build scalable authorization systems.