Tracking API calls by User ID in Go

Tracking API calls by anonymous usage is only part of the story when it comes to leveraging API analytics and metrics. A lot more value can come from knowing exactly what users are using specific endpoints and how they are using them.

To track API calls and attribute them to a user, the API calls must be authenticated. For this particular example, I’m going to use a JSON Web Token, also referred to as a “JWT”. The JWT should be attached to the request. The specific field we will use to track the user is the JWTs UserID field.

I will be working from the Go SDK code example from a previous guide, available here. To get user tracking working I will:

  • Configure the Moesif middleware on my Go API server to intercept the user ID coming from the JWT
  • Attach a JWT to my request, and send it to the API

A few things to mention in this guide, before we start.

Firstly, I will be generating a JWT from jwt.io. Normally you would use the authentication flow from your app in order to generate this upon login, or by other means that do not require manual creation. In order to make things easier though, I have opted for manual creation. You will want to ensure that your JWT does have the required fields, though, in order to make such an example work.

Secondly, this example API is not secured. It does accept a JWT is passed with the request but we are not doing any type of authorization checks in this example. When publishing an API to production, you will likely want to make sure you have the correct authorization flows in place.

Thirdly, our example assumes that your server and API are already integrated with Moesif. If you still need to integrate your API with Moesif, please take a look at our Go SDK integration guide, linked above, before proceeding if you want to follow along.

With that said, let’s jump into our example!

The Initial Code

Below is the code for our sample API. It is just a very simple, single endpoint, built with Go. As mentioned earlier, this server has already been integrated with Moesif based on the previous guide.

Once your backend is integrated with Moesif, you’ll begin to see events roll in. Those events will look like this on the Moesif Metrics screen:

<img class="lazyload blur-up" data-src="/docs/images/guides/1801/1-events-with-no-user-id.png" width="100%" alt="Moesif platform overview screen">

You’ll notice that the User ID field is blank. This means that these events are not tied to a specific user. Therefore, we cannot leverage user-specific metrics.

The code for this server implementation currently looks like this:

package main

import (
   "encoding/json"
   "log"
   "math/rand"
   "net/http"

   moesifmiddleware "github.com/moesif/moesifmiddleware-go"
)

var moesifOptions = map[string]interface{}{
   "Application_Id": "MOESIF_APPLICATION_ID",
}

const creditScoreMin = 500
const creditScoreMax = 900

type credit_rating struct {
   CreditRating int `json:"credit_rating"`
}

func handleRequests() {
   http.Handle("/creditscore", moesifmiddleware.MoesifMiddleware(http.HandlerFunc(getCreditScore), moesifOptions))
   log.Fatal(http.ListenAndServe(":8080", nil))
}

func main() {
   handleRequests()
}

func getCreditScore(w http.ResponseWriter, r *http.Request) {
   var creditRating = credit_rating{CreditRating: (rand.Intn(creditScoreMax-creditScoreMin) + creditScoreMin)}

   w.WriteHeader(http.StatusOK)
   json.NewEncoder(w).Encode(creditRating)
}

You will see under the moesifMiddleware configuration that your Moesif Application ID should be input. In order for us to configure user tracking, we need to add a few lines of code to the middleware configuration.

Adding User Tracking to Our Moesif Middleware

In order to add user tracking to our configuration, we need to supply an Identify_User parameter to the middleware. Doing that, our configuration will now look like this:

var moesifOptions = map[string]interface{}{
   "Application_Id": "MOESIF_APPLICATION_ID",
   "Identify_User":  identifyUser,
}

We will implement the idenfityUser function just above the moesifOptions. That function will look like this:

func identifyUser(request *http.Request, response moesifmiddleware.MoesifResponseRecorder) string {
	hmacSecretString := "mytokensecret"
	hmacSecret := []byte(hmacSecretString)
	authHeader, ok := request.Header["Authorization"]
    if ok {
        if strings.Contains(authHeader[0], "Bearer ") {
			tokenString := strings.Replace(authHeader[0], "Bearer ", "", -1)
			token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
				// check token signing method etc
				return hmacSecret, nil
			})

			if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
				userId, ok := claims["id"].(string)
				if ok {
					return userId
				} else {
					return ""
				}
			} else {
				print(err.Error())
				return ""
			}
		} else {
			return ""
		}
    } else {
        return ""
    }
}

This identifyUser function will parse out the field from the request you want to use to identify the user. The decoded JWT payload is available on the request via the user property.

NOTE: you don’t have to follow this exactly. You could pull the unique identifier from any part of the request, including the header if needed. The only thing necessary is to have the users’ ID be a unique and unchanging value.

With the code we supplied, Moesif will now be able to attach a user ID to an API request when the middleware intercepts the request.

The completed server code now looks like this:

package main

import (
   // go get -u github.com/golang-jwt/jwt/v5
   "encoding/json"
   "log"
   "math/rand"
   "net/http"
   "strings"

   "github.com/golang-jwt/jwt/v5"

   moesifmiddleware "github.com/moesif/moesifmiddleware-go"
)

func identifyUser(request *http.Request, response moesifmiddleware.MoesifResponseRecorder) string {
	hmacSecretString := "mytokensecret"
	hmacSecret := []byte(hmacSecretString)
	authHeader, ok := request.Header["Authorization"]
    if ok {
        if strings.Contains(authHeader[0], "Bearer ") {
			tokenString := strings.Replace(authHeader[0], "Bearer ", "", -1)
			token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
				// check token signing method etc
				return hmacSecret, nil
			})

			if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
				userId, ok := claims["id"].(string)
				if ok {
					return userId
				} else {
					return ""
				}
			} else {
				print(err.Error())
				return ""
			}
		} else {
			return ""
		}
    } else {
        return ""
    }
}

var moesifOptions = map[string]interface{}{
   "Application_Id": "MOESIF_APPLICATION_ID",
   "Identify_User":  identifyUser,
}

const creditScoreMin = 500
const creditScoreMax = 900

type credit_rating struct {
   CreditRating int `json:"credit_rating"`
}

func handleRequests() {
   http.Handle("/creditscore", moesifmiddleware.MoesifMiddleware(http.HandlerFunc(getCreditScore), moesifOptions))
   log.Fatal(http.ListenAndServe(":8080", nil))
}

func main() {
   handleRequests()
}

func getCreditScore(w http.ResponseWriter, r *http.Request) {
   var creditRating = credit_rating{CreditRating: (rand.Intn(creditScoreMax-creditScoreMin) + creditScoreMin)}

   w.WriteHeader(http.StatusOK)
   json.NewEncoder(w).Encode(creditRating)
}

Our next steps are to generate our JWT to attach to our request.

Creating the JWT

In this example, I’ll be using a JWT that contains a UserID field in its payload. My JWT will look like this:

<img class="lazyload blur-up" data-src="/docs/images/guides/1801/2-jwt-debug.png" width="100%" alt="Moesif platform overview screen">

I’ve created a JWT through jwt.io. Jwt.io allows you to create a JWT and edit the fields without having to use an Identity Provider or other source to create one. I’ve added a sample UserID field to the payload of my generated JWT. Now, I’ll attach the JWT to the request in Postman.

Sending a Request Through Postman

After opening up Postman, we will plug in the endpoint URL for our GET request. We will also go to the Authorization tab for the request and attach our JWT in the Token field. Also, note that the Type field should be set to “Bearer”.

<img class="lazyload blur-up" data-src="/docs/images/guides/1801/3-jwt-in-postman-request.png" width="100%" alt="Moesif platform overview screen">

Now, send off the request. At this point, the request will be received by the server. Once at the server, the JWT UserID field will be parsed out using the Moesif middleware’s identifyUser function and attached to the metric in Moesif.

Verifying That User Tracking is Working

In Moesif, our request should be showing in the Metrics dashboard. Now we can see that a userID has been bound to the request.

You’ll see that the UserID field passed through our JWT has now become the userID attached to the request we sent.

<img class="lazyload blur-up" data-src="/docs/images/guides/1801/4-events-with-user-id.png" width="100%" alt="Moesif platform overview screen">

At this point, we are now able to start leveraging user-specific metrics instead of having all of our data be anonymous. This can help to power many of the more advanced and valuable features within Moesif.

From Moesif

Other

Updated: