programming

17 Creating Certificate-based Authentication (SSL/TLS) Middleware in Go

In this article, we will learn how to create middleware for certificate-based authentication (SSL/TLS) using the httprouter library in Go. SSL/TLS is a widely used protocol for enhancing internet communication security, ensuring that only authorized parties can communicate with the server. This middleware will help us verify whether a client provides a valid certificate before accessing an endpoint.

What is Certificate-based Authentication?

Certificate-based authentication is a method of authentication that uses digital certificates to verify a client’s identity. These certificates typically contain a public key that encrypts communication between the client and the server, ensuring that only authorized clients can communicate securely.

SSL (Secure Socket Layer) and TLS (Transport Layer Security) are the main protocols used to secure network communications. By using certificates, the server can confirm that the client holds a valid certificate, reducing the risk of attacks like Man-in-the-Middle (MITM).

Steps to Create Middleware

To build this middleware, we will use the following components:

  • httprouter: A lightweight and fast HTTP router library in Go.
  • crypto/tls: A package for handling certificates and TLS communication.
  • net/http: To create an HTTP server in Go.

1. Installation and Project Setup

First, install the httprouter library by running the following command in your terminal:

go get -u github.com/julienschmidt/httprouter

Then, create a Go file named main.go to set up the HTTP server and middleware.

2. Creating SSL/TLS Middleware

This middleware will verify the client certificate. If the certificate is valid, the request proceeds to the next handler; otherwise, it responds with an HTTP 403 Forbidden status.

package main

import (
	"crypto/tls"
	"fmt"
	"log"
	"net/http"

	"github.com/julienschmidt/httprouter"
)

// CertificateAuthMiddleware verifies the client certificate
func CertificateAuthMiddleware(next httprouter.Handle) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
		// Retrieve the client certificate from the TLS connection
		clientCert := r.TLS.PeerCertificates
		if len(clientCert) == 0 {
			http.Error(w, "Client certificate required", http.StatusUnauthorized)
			return
		}

		// Log client certificate information (can be extended further)
		fmt.Println("Client certificate subject:", clientCert[0].Subject)

		// If the certificate is valid, proceed to the next handler
		next(w, r, ps)
	}
}

func main() {
	// Create a router
	router := httprouter.New()

	// Add middleware
	router.GET("/secure", CertificateAuthMiddleware(func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
		w.Write([]byte("You have access to the secure resource!"))
	}))

	// Set up the server with TLS
	server := &http.Server{
		Addr:      ":8080",
		Handler:   router,
		TLSConfig: &tls.Config{MinVersion: tls.VersionTLS13},
	}

	// Run the HTTPS server
	log.Println("Server running at https://localhost:8080")
	err := server.ListenAndServeTLS("certs/server.crt", "certs/server.key")
	if err != nil {
		log.Fatal("ListenAndServeTLS failed: ", err)
	}
}

3. Unit Testing the Middleware

Next, let’s write unit tests to ensure our middleware functions correctly.

Create a file main_test.go to write test cases using testing and net/http/httptest:

package main

import (
	"crypto/tls"
	"net/http"
	"net/http/httptest"
	"testing"
)

// TestCertificateAuthMiddleware tests SSL/TLS middleware
func TestCertificateAuthMiddleware(t *testing.T) {
	// Create a test router
	router := httprouter.New()
	router.GET("/secure", CertificateAuthMiddleware(func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
		w.Write([]byte("You have access to the secure resource!"))
	}))

	// Create a test server
	server := httptest.NewServer(router)
	defer server.Close()

	// Make an HTTPS request to the test server without a client certificate
	req, err := http.NewRequest("GET", server.URL+"/secure", nil)
	if err != nil {
		t.Fatalf("Error creating request: %v", err)
	}

	// Test that the server denies access without a certificate
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		t.Fatalf("Error making request: %v", err)
	}

	if resp.StatusCode != http.StatusUnauthorized {
		t.Errorf("Expected status %v, got %v", http.StatusUnauthorized, resp.StatusCode)
	}
}

4. Running the Server and Tests

Before running the server, ensure you have a valid certificate in the certs/ directory. If not, you can generate a self-signed certificate for testing purposes using OpenSSL:

openssl req -x509 -newkey rsa:4096 -keyout certs/server.key -out certs/server.crt -days 365

Then, run the application using:

go run main.go

To execute the unit test, run:

go test -v

5. Conclusion

In this article, we have learned how to create middleware for Certificate-based Authentication using Go with the httprouter library. We also created unit tests to ensure the middleware functions correctly. By understanding this concept, you can add an extra layer of security to your application, particularly for APIs that require certificate-based authentication.

For more Go tutorials and HTTP router usage, check out these related articles:

comments powered by Disqus