ruby 37 lines · 1 tab

JWT authentication with refresh tokens

Alex Kumar Jan 2026
1 tab
class JwtService
  ACCESS_TOKEN_LIFETIME = 15.minutes
  REFRESH_TOKEN_LIFETIME = 7.days
  SECRET = Rails.application.credentials.jwt_secret

  def self.encode_access_token(user_id)
    payload = {
      user_id: user_id,
      exp: ACCESS_TOKEN_LIFETIME.from_now.to_i,
      jti: SecureRandom.uuid
    }
    JWT.encode(payload, SECRET, 'HS256')
  end

  def self.encode_refresh_token(user_id)
    jti = SecureRandom.uuid
    payload = {
      user_id: user_id,
      exp: REFRESH_TOKEN_LIFETIME.from_now.to_i,
      jti: jti,
      type: 'refresh'
    }
    token = JWT.encode(payload, SECRET, 'HS256')
    Rails.cache.write("refresh_token:#{jti}", user_id, expires_in: REFRESH_TOKEN_LIFETIME)
    token
  end

  def self.decode(token)
    JWT.decode(token, SECRET, true, algorithm: 'HS256').first
  rescue JWT::DecodeError
    nil
  end

  def self.revoke_refresh_token(jti)
    Rails.cache.delete("refresh_token:#{jti}")
  end
end
1 file · ruby Explain with highlit

Stateless authentication with JWT tokens simplifies horizontal scaling but introduces security concerns around token lifetime and revocation. I use short-lived access tokens (15 minutes) combined with longer-lived refresh tokens stored in an encrypted HTTP-only cookie. When the access token expires, the client can request a new one using the refresh token without forcing the user to re-authenticate. The refresh token is stored in Redis with a blacklist mechanism so I can revoke sessions immediately when needed. This approach balances security and user experience—users stay logged in across sessions but compromised access tokens have limited blast radius. I also include a jti (JWT ID) claim to enable granular revocation.


Related snips

Share this code

Here's the card — post it anywhere.

JWT authentication with refresh tokens — share card
Link copied