You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Ruby中使用HTTParty集成外部API时存储JWT的最佳实践

Great question! When working with JWTs in Ruby—especially when integrating external APIs with HTTParty—there are several battle-tested practices for storing tokens, depending on your app’s architecture and token lifecycle. Let’s walk through the most common ones:

1. Environment Variables (Ideal for Long-Lived, Server-Side Tokens)

This is the go-to for production apps where the JWT is tied to your server’s identity (not individual users). Use the dotenv gem to keep tokens out of your codebase:

First, add dotenv-rails (or just dotenv for non-Rails Ruby apps) to your Gemfile, then create a .env file:

# .env (add this to .gitignore!)
EXTERNAL_API_JWT="your-valid-jwt-token-here"
CLIENT_ID="your-api-client-id"
CLIENT_SECRET="your-api-client-secret"

Then reference it in your HTTParty service class:

class ExternalAPIService
  include HTTParty
  base_uri "https://api.your-external-service.com"

  def initialize
    @auth_header = { "Authorization" => "Bearer #{ENV['EXTERNAL_API_JWT']}" }
  end

  def fetch_resource(resource_id)
    self.class.get("/resources/#{resource_id}", headers: @auth_header)
  end
end

Pro tip: Always keep .env out of version control to prevent accidental token leaks.

2. In-Memory Storage (For Short-Lived Tokens in Single-Process Apps)

If your JWT expires quickly (e.g., 15-30 minutes) and you’re running a single-process app (like a small Sinatra service), store the token in a class variable to avoid re-authenticating on every request. Add logic to check for expiration too:

require 'jwt'

class ExternalAPIService
  include HTTParty
  base_uri "https://api.your-external-service.com"
  @@cached_jwt = nil

  def self.get_valid_jwt
    # Return cached token if it exists and isn't expired
    return @@cached_jwt if @@cached_jwt && !token_expired?(@@cached_jwt)

    # Fetch a new token if cache is empty or expired
    auth_response = post("/auth", body: {
      client_id: ENV['CLIENT_ID'],
      client_secret: ENV['CLIENT_SECRET']
    })
    @@cached_jwt = auth_response.parsed_response['access_token']
    @@cached_jwt
  end

  private

  def self.token_expired?(token)
    decoded_payload = JWT.decode(token, nil, false)[0]
    Time.now.to_i > decoded_payload['exp']
  rescue JWT::DecodeError
    true # Treat invalid tokens as expired
  end

  # Example request using the valid JWT
  def fetch_data
    headers = { "Authorization" => "Bearer #{self.class.get_valid_jwt}" }
    self.class.get("/data", headers: headers)
  end
end

Note: This won’t work well in multi-process setups (like Puma in cluster mode) since each process maintains its own memory cache. For those cases, use a distributed cache.

3. Distributed Cache (For Multi-Process/Distributed Systems)

If you’re running multiple app servers or processes, use a shared cache like Redis or Memcached to store the JWT. This ensures all instances use the same token until it expires:

Using Redis with the redis-rb gem:

require 'redis'
require 'jwt'

class ExternalAPIService
  include HTTParty
  base_uri "https://api.your-external-service.com"
  REDIS_CLIENT = Redis.new(host: ENV['REDIS_HOST'], port: ENV['REDIS_PORT'])
  CACHE_KEY = "external_api_jwt"

  def self.get_valid_jwt
    cached_token = REDIS_CLIENT.get(CACHE_KEY)
    return cached_token if cached_token && !token_expired?(cached_token)

    # Fetch new token and cache it with matching expiration
    auth_response = post("/auth", body: {
      client_id: ENV['CLIENT_ID'],
      client_secret: ENV['CLIENT_SECRET']
    })
    new_token = auth_response.parsed_response['access_token']
    decoded_payload = JWT.decode(new_token, nil, false)[0]
    expires_in_seconds = decoded_payload['exp'] - Time.now.to_i

    REDIS_CLIENT.setex(CACHE_KEY, expires_in_seconds, new_token)
    new_token
  end

  # ... token_expired? method same as before
end

This keeps token fetching efficient across all your app instances.

4. Rails-Specific Options

If you’re building a Rails app, you have a couple of tailored choices:

  • Rails Cache: Use the built-in Rails.cache (configured to use Redis/Memcached in production) for a simpler approach:
    def self.get_valid_jwt
      Rails.cache.fetch(CACHE_KEY, expires_in: expires_in_seconds) do
        # Fetch new token logic here
      end
    end
    
  • Session/Cookies (User-Specific Tokens): If each user has their own JWT (e.g., after OAuth2 login), store it in the encrypted Rails session or secure cookies:
    # In your controller after user auth
    session[:user_external_jwt] = auth_response['access_token']
    
    # In your service class
    def initialize(user_jwt)
      @headers = { "Authorization" => "Bearer #{user_jwt}" }
    end
    
    Rails sessions are encrypted by default, so this is safe for user-specific tokens.

Critical Security Best Practices

  • Never hardcode tokens: Keep secrets out of your codebase and version control.
  • Encrypt sensitive storage: If you need to store tokens in a database (e.g., user-specific tokens), use a gem like attr_encrypted to encrypt the value.
  • Validate expiration: Always check if a token is expired before using it to avoid unnecessary failed requests.
  • Limit token scope: Request only the permissions your app needs when fetching the JWT—this minimizes damage if the token is compromised.

内容的提问来源于stack exchange,提问作者Brandon Teixeira

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.01 00:17:40