Passing refreshed JWT’s from Rails API using headers

I recently set up a basic Rails API that uses JWT’s to authenticate a user before granting access to a resource (see here). The API generates a JWT based off the user’s ID and a secret, then renders to back to the user via JSON. Today I’ll make two refactors to the process, so that 1) JWT’s are not only received through a header, but also passed through a header as well, and 2) adding an expiration and refresh strategy to the JWT such that if a logged in user is inactive for more than 30 minutes, their JWT expires.

As a refresher, my current JWT flow for login involves the user authenticating themselves by email and password, the API encoding a JWT and then passing it to the user via JSON, then the user storing the JWT in their local storage. Then when the user attempts to access a restricted route, they send their JWT back to the API in an authorization header, which the API must successfully decode before proceeding to pass back the resource.

Let’s first refactor to send back a generated JWT via headers with a couple simple tweaks. I already have the rack-cors gem installed and a basic configuration inside my application.rb file:

# inside my Application class in application.rb
config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*', :headers => :any, :methods => [:get, :post, :options]
end
end

I now need to configure it to expose a header by tweaking one line:

resource '*', :expose => ["jwt"], :headers => :any, :methods => [:get, :post, :options]

Now to my sessions controller. It currently passes the JWT via JSON:

jwt = Auth.encode({user: user.id})
render json: {jwt: jwt, message: ["Login successful!"]}

So all we need to do is refactor it as such:

response.headers["jwt"] = Auth.encode({user: user.id})
render json: {message: ["Login successful!"]}

Now onto the other refactor, adding expiration and a refresh strategy to my token. Currently my JWT encoding process has no options, just a bare bones token:

# in auth.rb file
def self.encode(payload)
JWT.encode(
payload,
auth_secret,
ALGORITHM)
end

With the above setup, a user’s token, once generated, never expires. Let’s change that by adding an expiration claim to it:

def self.encode(payload)
JWT.encode(
payload.merge(exp: 15.minutes.from_now.to_i),
auth_secret,
ALGORITHM)
end

Now the user will have to log back in after 15 minutes, regardless of their activity. Most apps have some sort of refresh strategy to avoid unnecessary logouts while still allowing the app owner to expire a token. There are lots of ways to go about this, but I’ll go with a very simple strategy here: every time the user makes a successful request, my API will generate a new JWT and pass it to the user:

class ProductsController < ApplicationController
before_action :current_user
  def index
products = @current_user.products.filtered_products.distinct
render json: products
end
  private
  def current_user
if @current_user ||= User.find_by(id:
Auth.decode(request.env["HTTP_AUTHORIZATION"])["user"])
response.headers["jwt"] = Auth.encode({user:@current_user.id})
else
render json: {error:{message:["You must have a valid token"]}}
end
end
end

Now every time the user fires off a successful GET request, a new token is generated and passed to them, and the clock resets. Pretty cool.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.