In the world of web development, secure communication between client and server is paramount. This was the need of a client of mine, a startup called Greencloud; check them out here: greencloudcomputing.io. This API is for a bigger project that I am working on with them. I will hopefully get to blog about it one day soon.
One common way to secure these communications is through the use of authentication mechanisms. Today, I'll walk you through creating a custom Authentication API in Go, providing a flexible and reusable way to handle different authentication methods.
Overview
Our goal is to create a middleware-based HTTP client that can handle various types of authentication, such as username/password and API key authentication. We'll leverage Go's powerful type system and interfaces to achieve this.
The Code
Here's the code we'll be working with:
package main
import (
"context"
"fmt"
"io"
"net/http"
)
// HTTPClient is an interface that can be used to perform HTTP requests
type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
}
// HTTPClientFunc is a function that implements HTTPClient
type HTTPClientFunc func(req *http.Request) (*http.Response, error)
// Do implements HTTPClient
func (fn HTTPClientFunc) Do(req *http.Request) (*http.Response, error) {
return fn(req)
}
// Middleware is a function that can mutate the request before it is sent
type Middleware func(HTTPClient) HTTPClient
// UserPassAuthentication is middleware that adds username/password authentication
// to the request
func UserPassAuthentication(username, password string) Middleware {
return func(client HTTPClient) HTTPClient {
return HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
req.Header.Set("Authorization", "Bearer "+username+password)
return client.Do(req)
})
}
}
// APIKeyAuthentication is middleware that adds APIKey-based authentication to requests
func APIKeyAuthentication(apikey string) Middleware {
return func(client HTTPClient) HTTPClient {
return HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
req.Header.Set("Authorization", "Bearer "+apikey)
return client.Do(req)
})
}
}
type Client struct {
client HTTPClient
}
func GCNewClient(httpClient *http.Client, middleware ...Middleware) Client {
client := Client{client: httpClient}
// Apply the middleware
for _, m := range middleware {
client.client = m(client.client)
}
return client
}
func (c *Client) Get(ctx context.Context, GCUrl string) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, "GET", GCUrl, nil)
if err != nil {
return nil, err
}
resp, err := c.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
// Read the response body
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read http response body: %w", err)
}
fmt.Println(string(body))
return body, err
}
func main() {
// Replace with your API key
apiKey := "your-api-key-here"
// Replace with the API endpoint
url := "https://your-api-endpoint.com"
client := GCNewClient(http.DefaultClient, APIKeyAuthentication(apiKey))
client.Get(context.TODO(), url)
}
Explanation
Interfaces and Middleware
We start by defining an HTTPClient
interface and a function type HTTPClientFunc
that implements this interface. This allows us to create flexible middleware that can modify requests before they are sent.
Authentication Middleware
We then define two middleware functions:
UserPassAuthentication: Adds a username and password to the request header.
APIKeyAuthentication: Adds an API key to the request header.
These middleware functions return a new HTTPClient
that modifies the request headers as needed.
Client Struct and Constructor
The Client
Struct holds an HTTPClient
, and the GCNewClient
Function applies the provided middleware to this client.
GET Request Method
The Get
Method constructs an HTTP GET request and uses the client to execute it. It reads the response body and returns it.
Putting It All Together
In the main
Function, we create a new client with API key authentication and use it to make a GET request to a specified endpoint. Remember to replace the placeholders with your actual API key and endpoint.
Conclusion
This approach provides a clean and modular way to handle different authentication mechanisms in Go. By using middleware, we can easily add or modify authentication methods without changing the core logic of our HTTP client.
Feel free to use and adapt this code for your projects. Happy coding!
I would like to thank Adam Talbot from the Kubernetes Go Community for helping with this project. Without his help, I would still be stuck looking at the NET/HTTP Package. You can find him here: https://uk.linkedin.com/in/mrtalbot
Please ensure you replace sensitive information, such as actual API keys, with placeholders before sharing your code publicly.