Home Technology Golang: API Auth with JWT & Middleware

Golang: API Auth with JWT & Middleware

by komalkumar436

Introduction

Public APIs are prone to attacks. One way is to impersonate the user and get access to the system resources via APIs. Lot of security methods are in use to protect resources via bad actors; One of the standard way of protecting APIs are to send Auth headers in the APIs. The traditional way to secure APIs are to use basic auth; where the client stores the auth details and send them over http/https for every server request; the server decodes the username and password to authenticate the resource access. This is bad as password is stored at client side and can easily be leaked from there. This technique is prone to attack in the middle and as the same details are sent over the wire repeatedly and the window of attack is very large.

JWT token provide a secure way to transfer the user details over wire for secure communications. For JWT introduction please read (https://jwt.io) . In this article we will discuss on the JWT middleware that decrypts the token and validate the token expiry and other mandatory claims before passing the request to handlers and how to instrument the user details to Golang context.

Choosing JWT Libraries and encryption

Various JWT RFCs implementations are available out there. Please refer (https://jwt.io) for all the open source implementations of the RFC. Out of all I find the https://github.com/lestrrat-go/jwx has implementation of all the JWX Spec. We will be using this library to build a JWT middleware.

JWT – json web tokens are by default not encrypted, these token can hold user claims that needs to be secure over wire. To secure the token and its claims, we will be encrypt the token. There are various encryption methods. Here we will consider to use asymmetric RSA256 encryption. In this mode, the authorisation server generates the JWT token (claims embedded) and encrypts the token with RSA256 private key. One way to generate Asymmetric RSA keys are as follows.

  • openssl genrsa -out privateKey.pem 512; -generates private key of 512bytes
  • openssl rsa -in privateKey.pem -pubout -out publicKey.pem; -generates public key

Authorisation Server will use the private key to encrypt the JWT token and client will send this token to the APIs in their authorisation Bearer token. The token will be decrypted at the Application Server using the public key and token is validated against the expiry of the token.

In comparison to asymmetric, symmetric encryption algorithms are faster and less secure. Based on the application needs choose between the two encryptions.

Assuming you are building a Golang Application using go modules and the http protocol for REST API. With this setup, it will be easy to integrate the following middleware. Let’s dive to build the middleware at application layer.

Step1: Create a type that holds the JWTDetails like public key, algorithm to use decrypt

import "github.com/lestrrat-go/jwx/jwt"
import "github.com/lestrrat-go/jwx/jwa"
// JWTDecode - object which implements the jwt token
type JWTDecode struct {
  PublicKey        interface{}
  Algorithm        jwa.SignatureAlgorithm
}

Step2: Implement Middleware to decrypt and process the token (skipping error checks..)


func (jwtDecode JWTDecode) InstrumentJWTClaims(next http.Handler) http.Handler {
   authHeader := r.Header.Get("Authorization") // Retrieve the auth header from the request.
   headers := strings.Fields(authHeader) // retrieve headers for bearer token.
   token, err := jwt.Parse(strings.NewReader(headers[1]), jwt.WithVerify(jwtDecode.Algorithm, jwtDecode.PublicKey))
   if err != nil {
     w.WriteHeader(http.StatusUnauthorized)
      w.Write([]byte("invalid token format"))
      return
   }
   if IsTokenExpired(token) == true {
      w.WriteHeader(http.StatusUnauthorized)
      w.Write([]byte("token expired"))
      return
   }
   instrumentUserName(r, token)
   next.ServeHTTP(w, r)
}

Step3: Validate token expiry

func IsTokenExpired(token jwt.Token) bool {
	expiry, exists := token.Get("exp")
	if exists == false {
		return true
	}
	exp, err := time.Parse(time.RFC3339, expiry.(string))
	if err != nil {
		return true
	}
	currTime := time.Now().UTC()
	if currTime.After(exp) == true {
		return true
	}
	return false
}

step4: Instrument user details to request scoped context.

func instrumentUserName(r *http.Request, token jwt.Token) {
	ctx := r.Context()
	username, _ := token.Get("username")
	ctx = context.WithValue(ctx, "username", username) // set  the username to context
	r.Header.Add("username", userid) //also set to headers
	r = r.WithContext(ctx) // set the context back to the request.
}

Setting Router Middleware

Step1: Create a Router and Start the server with a “/order” POST endpoint.

import "github.com/gorilla/mux"

func StartServer(cfg configuration) { // config read from the env 
  router:= mux.NewRouter()
  jwtMiddleware := &JWTDecode{PublicKey: cfg.publicKey, Algorithm: jwa.SignatureAlgorithm(cfg.algorithm)}
  router.Use(jwtMiddleware.InstrumentJWTClaims)
  router.HandleFunc("/order", createOrder).Methods(http.MethodPost)
}

On calling curl –location –request POST ‘http://localhost:8080/order&#8217; –header ‘Authorization: Bearer <token>’ \–header ‘Content-Type: application/json’ \–data-raw ‘{ “items_info”:[1,2,3,4]}’.

The request will flow through the jwtMiddleware and then to the /order handler i.e createOrder

//Retrieving the jwt claims instrumented in the context.
func createOrder(w http.ResponseWriter, r *http.Request) {
   ctx := r.Context()
   username := ctx.Value("username").(string) //get the username that is set to request context as part of jwt middleware.
...
}

Here we come to the end; to summarise we discussed on

  • how to create a jwt middleware
  • how to instrument it in the router middleware
  • how to read the claims set in the request scope context back and use it further.

JWT token authentication is a better way than basic auth to authorise and authenticate clients.

You may also like

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: