programming

16 Building HMAC Authentication Middleware Using httprouter in Golang

In this article, we will discuss how to build an HMAC (Hash-based Message Authentication Code) authentication middleware using Go and the httprouter library. This middleware functions to authenticate HTTP requests based on an HMAC signature, ensuring data integrity and verifying that the data originates from a trusted source.

What is HMAC?

HMAC is a method used to provide authentication and integrity to messages or data transmitted over a network. In the context of APIs, HMAC is used to verify that the received data has not been modified in transit and that it was indeed sent by an authorized sender.

HMAC works by using a hashing algorithm (e.g., SHA-256) combined with a secret key. This process generates a signature that can be verified by the recipient. If the signature matches, the data is considered valid.

Project Setup

To get started, ensure you have a Go development environment set up. You also need to install httprouter by running:

go get github.com/julienschmidt/httprouter

Next, create a new project folder and add a main.go file inside it.

Step 1: Implementing HMAC Middleware

The middleware is responsible for inspecting incoming requests, verifying the HMAC signature, and ensuring that the request comes from a legitimate source. Below is the basic implementation:

package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"

	"github.com/julienschmidt/httprouter"
)

// Secret key for HMAC verification
var secretKey = []byte("your-secret-key")

// HMAC Middleware
func HMACMiddleware(next httprouter.Handle) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
		// Retrieve signature from the "X-Signature" header
		signature := r.Header.Get("X-Signature")
		if signature == "" {
			http.Error(w, "Missing signature", http.StatusUnauthorized)
			return
		}

		// Read request body to generate HMAC signature
		body, err := ioutil.ReadAll(r.Body)
		if err != nil {
			http.Error(w, "Unable to read body", http.StatusInternalServerError)
			return
		}

		// Create HMAC using the secret key
		mac := hmac.New(sha256.New, secretKey)
		mac.Write(body)
		expectedSignature := hex.EncodeToString(mac.Sum(nil))

		// Verify if the signature matches
		if !hmac.Equal([]byte(signature), []byte(expectedSignature)) {
			http.Error(w, "Invalid signature", http.StatusUnauthorized)
			return
		}

		// Proceed to the next handler
		next(w, r, ps)
	}
}

Step 2: Setting Up Router and Handlers

Next, set up the httprouter router and a handler to process requests successfully authenticated via HMAC:

func HelloHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	fmt.Fprintln(w, "Hello, authenticated user!")
}

func main() {
	router := httprouter.New()

	// Apply HMAC middleware to this endpoint
	router.GET("/hello", HMACMiddleware(HelloHandler))

	// Start the server
	http.ListenAndServe(":8080", router)
}

Here, we add a /hello route protected by the HMAC middleware. Only requests with a valid signature will be granted access.

Step 3: Testing HMAC with Unit Tests

To ensure our HMAC middleware works correctly, we will write unit tests covering two scenarios:

  1. A legitimate request with a valid signature.
  2. A request with an invalid signature.

Here is the unit test code:

package main

import (
	"net/http"
	"net/http/httptest"
	"testing"
	"strings"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"

	"github.com/julienschmidt/httprouter"
	"github.com/stretchr/testify/assert"
)

// Function to create a request with HMAC header
func makeRequest(signature string, body string) *http.Request {
	req := httptest.NewRequest("GET", "/hello", strings.NewReader(body))
	req.Header.Set("X-Signature", signature)
	return req
}

// Unit Test for HMAC Middleware
func TestHMACMiddleware(t *testing.T) {
	// Create router and handler for testing
	router := httprouter.New()
	router.GET("/hello", HMACMiddleware(HelloHandler))

	// Test case 1: Valid request
	body := "Hello World"
	mac := hmac.New(sha256.New, secretKey)
	mac.Write([]byte(body))
	validSignature := hex.EncodeToString(mac.Sum(nil))
	req := makeRequest(validSignature, body)

	// Simulate request
	rr := httptest.NewRecorder()
	router.ServeHTTP(rr, req)

	// Verify response
	assert.Equal(t, http.StatusOK, rr.Code)
	assert.Contains(t, rr.Body.String(), "Hello, authenticated user!")

	// Test case 2: Invalid signature
	invalidSignature := "invalid-signature"
	req = makeRequest(invalidSignature, body)

	// Simulate request
	rr = httptest.NewRecorder()
	router.ServeHTTP(rr, req)

	// Verify response
	assert.Equal(t, http.StatusUnauthorized, rr.Code)
	assert.Contains(t, rr.Body.String(), "Invalid signature")
}

Running the Unit Tests

To run the unit tests, execute:

go test -v

Conclusion

In this article, we learned how to build HMAC authentication middleware using Go and httprouter. This middleware ensures data integrity and authentication for HTTP requests. We also explored unit testing to verify its functionality.

For more in-depth Go tutorials, visit Tutorial Golang. If you want to learn more about HTTP routers in Go, check out Web HTTP Router in Golang.

By understanding HMAC concepts and their implementation in middleware, you can build more secure and reliable systems. Hope this article was helpful and easy to follow!

comments powered by Disqus