Brute Force Attack in Rails with Jwt Tokens
Brute Force Attack in Rails with Jwt Tokens — how this specific combination creates or exposes the vulnerability
A brute force attack against a Rails API that uses JWT tokens involves systematically guessing token values or the secrets that sign them to achieve unauthorized access. Because JWTs are often transmitted in an Authorization: Bearer header, an attacker who can make repeated unauthenticated requests to an endpoint that accepts or introspects tokens may attempt to discover valid tokens or the signing secret. This risk is amplified when token entropy is low, tokens lack proper expirations, or the application exposes informative error messages that reveal whether a guessed token or signature is partially valid.
In Rails, common misconfigurations that enable this attack vector include permitting token validation on public endpoints, failing to enforce strict issuer/audience checks, and using weak algorithms (e.g., switching from HS256 to none or using predictable secrets). For example, if a route like GET /api/profile accepts a JWT without rate limiting, an attacker can script thousands of requests per minute, trying random tokens or incrementally modified valid tokens. The Rails logs may inadvertently disclose whether a token was malformed or recognized, helping refine guesses. Additionally, if the application decodes tokens without verifying signatures (e.g., using decode without verify), an attacker might exploit algorithm confusion to forge tokens.
The OWASP API Security Top 10 category Broken Object Level Authorization (BOLA) and related IDOR issues intersect with brute force against JWTs: if object-level access controls are enforced only at the application layer and tokens do not embed sufficient authorization context, an attacker can brute force identifiers within a token or swap tokens to access other users’ resources. Input validation weaknesses further enable enumeration attacks where responses differ based on token validity or resource ownership. Since middleBrick tests Authentication, Authorization, and Input Validation in parallel, such combinations are detectable as high-risk findings that require remediation guidance focused on token integrity and request throttling.
Jwt Tokens-Specific Remediation in Rails — concrete code fixes
Remediation centers on strengthening token generation, mandating strict verification, and reducing brute force opportunities. Use strong secrets or asymmetric keys, enforce short expirations, bind tokens to client context, and apply rate limiting to token-validation endpoints. Below are concrete Rails code examples that reflect these controls.
1. Secure token generation and verification with HS256
# config/initializers/jwt.rb
require 'jwt'
module JwtManager
ALGORITHM = 'HS256'
SECRET = Rails.application.credentials.jwt_secret_key_base
def self.encode(payload)
payload[:exp] = Time.now.utc.to_i + (15 * 60) # 15 minute expiration
JWT.encode(payload, SECRET, ALGORITHM)
end
def self.decode(token)
# verify signature and expiration; raise on invalid
JWT.decode(token, SECRET, true, { algorithm: ALGORITHM })
end
end
2. Enforce issuer and audience claims
# app/services/jwt_validator.rb
class JwtValidator
def initialize(token)
@token = token
end
def valid?
decoded = JwtManager.decode(@token)
header = JWT.decode(@token, nil, false).first
payload = decoded.first
payload['iss'] == 'my-rails-api' && payload['aud'] == 'web-client'
rescue JWT::ExpiredSignature, JWT::VerificationError
false
end
end
3. Rate limiting token validation attempts
# app/controllers/api/concerns/rate_limited_token_check.rb
module RateLimitedTokenCheck
extend ActiveSupport::Concern
RATE_LIMIT = 30 # requests
WINDOW = 60 # seconds
included do
before_action :check_token_rate_limit
end
def check_token_rate_limit
key = "rate_limit_token_#{request.headers['Authorization']&.split(' ')&.last || request.ip}"
count = Rails.cache.fetch(key, expires_in: WINDOW.seconds) { 0 }
if count >= RATE_LIMIT
render json: { error: 'Too many requests' }, status: 429
else
Rails.cache.write(key, count + 1, expires_in: WINDOW.seconds)
end
end
end
4. Bind tokens to a fingerprint (e.g., device/IP hash)
# app/models/user.rb
class User < ApplicationRecord
def generate_token_with_fingerprint
fingerprint = Digest::SHA256.hexdigest("#{request_user_agent}#{request_ip}#{Time.now.to_date}")
JwtManager.encode(sub: id, fingerprint: fingerprint)
end
def self.verify_token_with_fingerprint(token, user_agent, ip)
payload = JwtManager.decode(token).first
expected = Digest::SHA256.hexdigest("#{user_agent}#{ip}#{Time.at(payload['exp'] - 900).to_date}")
payload['fingerprint'] == expected
end
end
5. Avoid algorithm confusion and always verify signatures
# Do NOT do this:
# JWT.decode(token, nil, false)
# Always do this:
begin
decoded = JWT.decode(token, Rails.application.credentials.jwt_secret_key_base, true, { algorithm: 'HS256' })
rescue JWT::DecodeError
# reject token
end
Implementing these patterns reduces the attack surface for brute force against JWTs in Rails and aligns findings from middleBrick scans with concrete remediation guidance. Use the CLI (middlebrick scan <url>) and the Web Dashboard to track improvements over time; the Pro plan supports continuous monitoring and GitHub Action integration to fail builds if risk scores degrade.