Tracking API calls by User ID in Rails
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 Ruby Rack SDK code example from a previous guide. To get user tracking working I will:
- Add the JWT dependency to my project using
gem
- Configure the Moesif middleware on my Rails 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 that 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 Ruby Rack SDK integration guide, linked above, before proceeding if you want to follow along.
With that said, let’s jump into our example!
Step 1: 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/2201/live-event-log-no-user.png" width="100%" alt="Example">
You’ll notice that the User field is blank. This means that these events are not tied to a specific user. Therefore, we cannot leverage user-specific metrics.
Our first order of business is to install the JWT dependency. To do this, In the projects Gemfile in the root directory, we will add an entry for jwt
. It should look like the line below.
gem jwt
Once the above line is added to the Gemfile and saved, you’ll install the dependency to the project using the below command.
bundle install
At this point, we are ready to move on to actually coding our user-tracking implementation.
Step 2: Adding User Tracking to Our Moesif Middleware
In order to add user tracking to our Moesif middleware configuration, we need to supply an identify_user Proc to the middleware. In the application.rb file where your Moesif middleware configuration is, we will add an identify_user implementation just below your initial @moesif_options
entry. The code will look like this:
@moesif_options = {
'application_id' => 'YOUR_MOESIF_APPLICATION_ID'
}
@moesif_options['identify_user'] = Proc.new{|env, headers, body|
}
Next, We will implement the idenfityUser Proc. The Proc will grab a JWT passed in the HTTP_AUTHORIZATION
header. It will then will grab the token, omitting the bearer
prefix that will be on the raw string in the header. This is why you see the [7…]
substring function being used. Lastly, we will use JWT.decode
to decode the token and grab the userId
data, seen in the last line of code.
@moesif_options['identify_user'] = Proc.new{|env, headers, body|
request_auth_header = env.select { |key, value| key.include?('HTTP_AUTHORIZATION') }
token = request_auth_header['HTTP_AUTHORIZATION'][7...]
hmac_secret = 'shared-secret-123'
decoded_token = JWT.decode token, hmac_secret, false, { algorithm: 'HS256' }
p decoded_token[0]['userId']
}
You don’t have to follow this exactly. You could pull the unique identifier from any part of the request. The only thing necessary is to have the user’s 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:
require_relative "boot"
require "rails/all"
Bundler.require(*Rails.groups)
module MoesifApiProject
class Application < Rails::Application
config.load_defaults 7.0
config.api_only = true
@moesif_options = {
'application_id' => 'YOUR_MOESIF_APPLICATION_ID'
}
@moesif_options['identify_user'] = Proc.new{|env, headers, body|
request_auth_header = env.select { |key, value| key.include?('HTTP_AUTHORIZATION') }
token = request_auth_header['HTTP_AUTHORIZATION'][7...]
hmac_secret = 'shared-secret-123'
decoded_token = JWT.decode token, hmac_secret, false, { algorithm: 'HS256' }
p decoded_token[0]['userId']
}
config.middleware.use MoesifRack::MoesifMiddleware, @moesif_options
end
end
Our next steps are to generate our JWT to attach to our request.
Step 3: 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/2201/jwt-with-user-id.png" width="100%" alt="Example">
The Shared Secret in the JWT generated on jwt.io and the hardcoded
hmac_secret
we added in the ruby code should match. In this case, it isshared-secret-123
.
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 add an Authorization header to the request and append our JWT to the Token field, with a bearer
prefix.
<img class="lazyload blur-up" data-src="/docs/images/guides/2201/postman-authorization-header.png" alt="Example">
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.
If you do not send the
bearer
string in the Authorization header, you will need to tweak theidentify_user
proc and remove the[7…]
so that the first 7 characters of the token are not truncated.
Step 4: Verifying The User Tracking is Working
In Moesif, our request should be showing in the Metrics dashboard. Now we can see that a user 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/2201/live-event-log-with-user.png" alt="Example">
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.