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.
- Checks whether the API has an
X-API-Key
header - If it does not exist, it will provide
Unauthorized
error information - 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
- Unauthorized when we access the service without sending the
X-API-Key
Header with the values3cr3t
- Successfully accessing the service by sending the appropriate Header
- Unauthorized when we send the
X-API-Key
Header with the wrong value for examplesecret
.
Here are the results of our test
Testing without theX-API-Key
header resulted in an errorSuccessful test with X-API-Key
header with appropriate valueTest with wrong header key