Skip to content
Santekno.com | Level Up Your Engineering Skills
EN
📖 0%
30 Mar 2024 · 5 min read ·Article 87 / 119
Go

10 Adding Simple Authentication

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

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.

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

go
 1package middleware_chain
 2
 3import (
 4	"net/http"
 5
 6	"github.com/julienschmidt/httprouter"
 7)
 8
 9// Constructor is type of httprouter handler
10type Constructor func(httprouter.Handle) httprouter.Handle
11
12// Chain is struck for list of middleware
13type Chain struct {
14	constructors []Constructor
15}
16
17// New is for innitial new chain of
18func New(constructors ...Constructor) Chain {
19	return Chain{append(([]Constructor)(nil), constructors...)}
20}
21
22// Then is for http router handler
23func (c Chain) Then(h httprouter.Handle) httprouter.Handle {
24	if h == nil {
25		return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {}
26	}
27	for i := range c.constructors {
28		h = c.constructors[len(c.constructors)-1-i](h)
29	}
30
31	return h
32}
33
34// Append is for add chain router handler
35func (c Chain) Append(constructors ...Constructor) Chain {
36	newCons := make([]Constructor, 0, len(c.constructors)+len(constructors))
37	newCons = append(newCons, c.constructors...)
38	newCons = append(newCons, constructors...)
39
40	return Chain{newCons}
41}
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.
go
1	// initialize http router
2	router := httprouter.New()
3
4	// initialize middleware chain
5	m := middleware_chain.New(
6		// middleware function that we will add
7	)

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.

go
 1func AuthenticationBasic(next httprouter.Handle) httprouter.Handle {
 2	return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
 3		if r.Header.Get(XApiKey) != Secret {
 4			var statusCode = http.StatusUnauthorized
 5			var response models.HeaderResponse
 6			response.Code = statusCode
 7			response.Status = "Unauthorized"
 8			util.Response(w, response, statusCode)
 9			return
10		}
11
12		next(w, r, params)
13	}
14}

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.

go
 1func NewRouter(articleHandler *httpHandler.Delivery) *httprouter.Router {
 2	// initialize http router
 3	router := httprouter.New()
 4
 5	// initialize middleware chain
 6	m := middleware_chain.New(
 7		middleware.AuthenticationBasic,
 8	)
 9
10	// entrypoint
11	router.GET("/api/articles", m.Then(articleHandler.GetAll))
12	router.GET("/api/articles/:article_id", m.Then(articleHandler.GetByID))
13	router.POST("/api/articles/", m.Then(articleHandler.Store))
14	router.PUT("/api/articles/:article_id", m.Then(articleHandler.Update))
15	router.DELETE("/api/articles/:article_id", m.Then(articleHandler.Delete))
16
17	return router
18}

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

go
 1func main() {
 2	fileEnv := ".env"
 3	if os.Getenv("environment") == "development" {
 4		fileEnv = "../.env"
 5	}
 6
 7	err := godotenv.Load(fileEnv)
 8	if err != nil {
 9		log.Fatalf("error loading .env file")
10	}
11
12	// initialize the database
13	db := database.New()
14
15	// initialize repository
16	repository := mysqlRepository.New(db)
17	// initialize usecase
18	articleUsecase := articleUsecase.New(repository)
19	// handler initialization
20	articleHandler := httpHandler.New(articleUsecase)
21	// initialize new router
22	router := NewRouter(articleHandler)
23
24	server := http.Server{
25		Addr:    "localhost:3000",
26		Handler: router,
27	}
28
29	err = server.ListenAndServe()
30	if err != nil {
31		panic(err)
32	}
33}

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

Related Articles

💬 Comments