How I built an Authentication API in Go

How I built an Authentication API in Go

·

4 min read

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:

  1. UserPassAuthentication: Adds a username and password to the request header.

  2. 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.