programming

10 Adding Simple Authentication

At this stage we will try to add simple Authentication by using middleware in Golang. You need to know that middleware is the process of intercepting an API which before the service when accessed goes to the *handler * layer, it will pass through this * middleware * layer (which we will create) to capture and process something for certain needs. For example, in this case we will intercept the service process to see if the API has a header with the key condition X-API-Key.

So, the middleware that we will create has the following rules.

  1. Checks whether the API has an X-API-Key header
  2. If it does not exist, it will provide Unauthorized error information
  3. And if it exists and matches the predetermined value of s3cr3t and is the same, then the process will continue.

Here is more or less the flow middleware that we will create.

---
Title: Authentication Middleware
---
stateDiagram-v2
    [*] --> hasHeader
    hasHeader --> unAuthorized
    unAuthorized --> [*]

    hasHeader --> HeaderXAPIKey
    HeaderXAPIKey --> unAuthorized

    HeaderXAPIKey --> SuccessContinue
    SuccessContinue --> [*]

Middleware Chain Creation

First we will create the pkg/middleware-chain folder with the name middleware_chain.go. Here are the contents of the file.

package middleware_chain

import (
	"net/http"

	"github.com/julienschmidt/httprouter"
)

// Constructor is type of httprouter handler
type Constructor func(httprouter.Handle) httprouter.Handle

// Chain is struck for list of middleware
type Chain struct {
	constructors []Constructor
}

// New is for innitial new chain of
func New(constructors ...Constructor) Chain {
	return Chain{append(([]Constructor)(nil), constructors...)}
}

// Then is for http router handler
func (c Chain) Then(h httprouter.Handle) httprouter.Handle {
	if h == nil {
		return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {}
	}
	for i := range c.constructors {
		h = c.constructors[len(c.constructors)-1-i](h)
	}

	return h
}

// Append is for add chain router handler
func (c Chain) Append(constructors ...Constructor) Chain {
	newCons := make([]Constructor, 0, len(c.constructors)+len(constructors))
	newCons = append(newCons, c.constructors...)
	newCons = append(newCons, constructors...)

	return Chain{newCons}
}

In the middleware that we have created, it is used for general handling which when later we have more than one middleware, there is no need to extend more so that it results in a router that is quite long in the naming but we only need to add it when initializing the router easily like this.

	// initialize http router
	router := httprouter.New()

	// initialize middleware chain
	m := middleware_chain.New(
		// middleware function that we will add
	)

Create a Simple Authentication Middleware

In the function that we will create, namely * middleware * which needs to do Authentication service so that not just anyone accesses our services and data so that it is more secure.

In accordance with what has been explained above, we will create this authentication middleware process for our service where we will save the file in the middleware/auth.go folder and fill the file with the code below.

func AuthenticationBasic(next httprouter.Handle) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
		if r.Header.Get(XApiKey) != Secret {
			var statusCode = http.StatusUnauthorized
			var response models.HeaderResponse
			response.Code = statusCode
			response.Status = "Unauthorized"
			util.Response(w, response, statusCode)
			return
		}

		next(w, r, params)
	}
}

Very simple because we want to try to create simple and easy middleware first so that friends can understand the process of this middleware.

Turning Router Initialization into its Own Function

At this stage we will separate the initialization of the Router API into a separate function so that it is easy to recognize and not too long when it has many endpoints causing the main.go file to become long and large. So we will separate the Router API initialization code with the code as below.

func NewRouter(articleHandler *httpHandler.Delivery) *httprouter.Router {
	// initialize http router
	router := httprouter.New()

	// initialize middleware chain
	m := middleware_chain.New(
		middleware.AuthenticationBasic,
	)

	// entrypoint
	router.GET("/api/articles", m.Then(articleHandler.GetAll))
	router.GET("/api/articles/:article_id", m.Then(articleHandler.GetByID))
	router.POST("/api/articles/", m.Then(articleHandler.Store))
	router.PUT("/api/articles/:article_id", m.Then(articleHandler.Update))
	router.DELETE("/api/articles/:article_id", m.Then(articleHandler.Delete))

	return router
}

And don’t forget we change the Router API initialization in the main.go file like this.

func main() {
	fileEnv := ".env"
	if os.Getenv("environment") == "development" {
		fileEnv = "../.env"
	}

	err := godotenv.Load(fileEnv)
	if err != nil {
		log.Fatalf("error loading .env file")
	}

	// initialize the database
	db := database.New()

	// initialize repository
	repository := mysqlRepository.New(db)
	// initialize usecase
	articleUsecase := articleUsecase.New(repository)
	// handler initialization
	articleHandler := httpHandler.New(articleUsecase)
	// initialize new router
	router := NewRouter(articleHandler)

	server := http.Server{
		Addr:    "localhost:3000",
		Handler: router,
	}

	err = server.ListenAndServe()
	if err != nil {
		panic(err)
	}
}

Testing

After we have installed the Authentication Middleware we need to test it on each endpoint that we have created. Testing it means there are two scenarios, namely

  1. Unauthorized when we access the service without sending the X-API-Key Header with the value s3cr3t
  2. Successfully accessing the service by sending the appropriate Header
  3. Unauthorized when we send the X-API-Key Header with the wrong value for example secret.

Here are the results of our test

without header api key

Testing without the X-API-Key header resulted in an error

with header api key success

Successful test with X-API-Key header with appropriate value

wrong header api key

Test with wrong header key

comments powered by Disqus