Securing API end points using Negroni, Gorilla Mux, and JWT Middleware

Disclaimer: I am not a security expert. Please do not consider this as a recommendation or best practice. Here I am just sharing my learning experience while developing some hobby/pet/side projects.

These days when I develop RESTful APIs, I prefer Go programming language in the back-end. I don’t use any “web framework” , rather I prefer the net/http package in the standard library. Along with the standard library, I also use some third-party libraries. Here is the list:

There are few others, but the above four are most used in my hobby/pet/side projects. You can checkout my current pet project in GitHub to see others.

For this example, Envconfig is not required. You can install the other packages like this:

go get github.com/auth0/go-jwt-middleware
go get github.com/gorilla/mux
go get github.com/urfave/negroni

The JTW Middleware package installs jwt-go package as a dependency.

Here is the complete example code:

You can run the above program like this:

go run protectedapi.go

The /api/without-auth can be accessed without JWT token. But to access /api/with-auth you need a JWT token which you can generate from jwt.io

You can access the /api/with-auth API end point using curl like this:

export TKN=<the-JWT-token-generated-from-jwt.io>
curl -H "Authorization: Bearer $TKN" http://localhost:7080/api/with-auth

To avoid typing the JWT bearer token all the time in authorization header, I normally set it as an environment variable. In the above example, I set TKN as an environment variable with the value I generated from jwt.io website.

Here are few outputs based on different kinds of inputs:

export TKN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"
$ curl -H "Authorization: Bearer $TKN" http://localhost:7080/api/with-auth
auth required
$ curl http://localhost:7080/api/with-auth
Required authorization token not found
$ curl -H 'Authorization: Bearer' http://localhost:7080/api/with-auth
Authorization header format must be Bearer {token}
$ curl -H 'Authorization: Bearer a' http://localhost:7080/api/with-auth
token contains an invalid number of segments
$ curl -H 'Authorization: Bearer a.b.c' http://localhost:7080/api/with-auth
illegal base64 data at input byte 1

In the above example, the first one is a correct token generated from jwt.io (When generating token use HS256 algorithm with “secret” as the secret key). That’s why we got auth required message. All other inputs are wrong and so it produced various kinds of error messages.

Code Walk Through

Just like any other Go package, the code starts with package name declaration. In this case the package name is main as we are going to run it directly. To make it an executable program, we also declare a main function below. After the package name declaration, net/http and other third-party packages are imported using factored import style (multiple imports within brackets). As you can see, two import aliases (middleware & jwt) are also specified for the convenience of using them later.

The main function started with the initialization of two routers like this:

r := mux.NewRouter()
ar := mux.NewRouter()

The first router (r) will be used to configure API end points without authentication. Whereas the second router (ar) will be used for configuring API end points which requires authentication.

The line number 16–21 is the initialization of the JWT middle-ware which is copied below for easy lookup:

mw := jwtmiddleware.New(jwtmiddleware.Options{
ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
return []byte("secret"), nil
},
SigningMethod: jwt.SigningMethodHS256,
})

JWT has support for multiple signing methods/algorithms. In the current example we are using HMAC SHA-256 algorithm which is based on a symmetric secret key. The key is provided as the first return value of ValidationKeyGetter function. We chose this particular algorithm for the convenience of demonstration. So that you can easily generate a JWT token from jwt.io website by providing the secret, which happened to be the string “secret” in this case.

For real use instead of the above HMAC algorithm, you can use RS256 which uses public/private key pair using RSA.

The line number 23–25 register a handler with a router that doesn’t require authentication. The line number 27–29 register another handler with another router that requires authentication. We will see in a moment how these two routers achieving this.

After this, in the line number 31, we are going to create a Negroni middle-ware chain with two middle-wares. Let’s take a closer look:

an := negroni.New(negroni.HandlerFunc(mw.HandlerWithNext), negroni.Wrap(ar))

The mw.HandlerWithNext function is converted to a middle-ware by using the negroni.HandlerFunc helper function. The second middle-ware is constructed using the negroni.Wrap helper function wrapping the ar router. The final object will be conforming the the http.Handler interface. This makes it possible to register a path prefix using the r router as given like this:

r.PathPrefix("/api").Handler(an)

This ensures that the API end points that doesn’t exactly match anything directly registered with r router also pass through the middle-ware registered in the an middle-ware chains. To understand this better, consider the two API end points registered in line numbers 23–29. The API end point /api/without-auth is registered with r router. Even though it starts with /api prefix, exact end point is fully matched and so the authentication related middle-wares will not be used. Where as in the case /api/with-auth is registered with ar router and so it’s going to be used.

Then another Negroni middle-ware chain is constructed. And this is used for registering the r router. Instead of Classic,which gives three middle-wares you can use negroni.New and negroni.Use to create middle-ware chains.

n := negroni.Classic()
n.UseHandler(r)

Finally, the application is run using the Run helper function.

n.Run(":7080")

Instead of the above function call, you can also use: http.ListenAndServe(":7080", n)

Conclusion

Securing API end points is easy in Go with the help of Negroni, Gorilla Mux, and JWT Middleware. These libraries has good documentation which you can see in the GitHub README files or the godoc.org website.