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:
- A legitimate request with a valid signature.
- 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!