Bola Idor in Hanami with Bearer Tokens
Bola Idor in Hanami with Bearer Tokens — how this specific combination creates or exposes the vulnerability
Broken Level of Authorization (BOLA) is an API security risk where an attacker can access or modify resources that should be restricted to a different user. In Hanami, a Ruby web framework that emphasizes explicit architecture, BOLA often arises when authorization checks are missing or incorrectly scoped around resource identifiers. When Bearer Tokens are used for authentication, the presence of a valid token does not imply access to every resource; the token must be tied to the correct subject and the application must enforce per-resource ownership or tenant boundaries.
Consider a Hanami endpoint that retrieves a user profile by ID:
GET /api/v1/profiles/123
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidXNlcjEyMyJ9.example
If the endpoint resolves the token to a user identity (e.g., user_id: "user123") but then fetches the profile record by the path parameter (123) without verifying that profile belongs to user123, a BOLA vulnerability exists. The Bearer Token ensures the request is authenticated, but it does not enforce authorization at the resource level. An attacker who knows or guesses another user’s ID (e.g., /profiles/456) can read that profile if the backend does not validate ownership.
Hanami’s explicit routing and use of objects can encourage a pattern where IDs are passed directly to repositories or services. Without an explicit policy check that maps the token’s subject to the requested resource, the unauthenticated attack surface includes logic flaws in authorization. This becomes more impactful when IDs are predictable (sequential integers or UUIDs not namespaced per user) and when the API returns sensitive data such as email, roles, or PII. The scanner categories in middleBrick’s LLM/AI Security and Property Authorization checks are designed to surface such gaps by correlating runtime requests with OpenAPI/Swagger definitions and observed responses, including cross-references between spec definitions and runtime behavior.
In practice, BOLA with Bearer Tokens in Hanami can also compound privilege escalation if token handling is inconsistent across endpoints. For example, an endpoint that mistakenly uses a token to infer an admin flag without verifying scope or tenant can allow horizontal or vertical escalation. Because Hanami apps often rely on domain entities and services, developers must ensure that each service explicitly validates that the authenticated subject (from the token) is authorized for the specific operation and resource, rather than relying on route obscurity or implicit trust in the token alone.
Bearer Tokens-Specific Remediation in Hanami — concrete code fixes
To remediate BOLA when using Bearer Tokens in Hanami, enforce explicit ownership or tenant checks in service objects or operations, and avoid leaking internal IDs that can be traversed without validation. Below are concrete, working examples that demonstrate secure patterns.
1. Map token subject to resource ownership
Decode the token, extract the subject, and use it to scope queries. Never trust the ID from the route alone.
# Gemfile
gem "jwt"
# app/services/profile_finder.rb
require "jwt"
class ProfileFinder
def initialize(request_headers)
@request_headers = request_headers
end
def call(profile_id)
token = extract_token
payload = decode_token(token)
user_id = payload["sub"] || payload["user_id"]
# Enforce ownership: only return the profile if it belongs to the token's subject
profile = ProfileRepository.new.find_by(id: profile_id, user_id: user_id)
raise Hanami::Repository::NoMatchingTuple.new if profile.nil?
profile
end
private
def extract_token
auth_header = @request_headers["Authorization"]
return nil unless auth_header&&auth_header.start_with?("Bearer ")
auth_header.split(" ").last
end
def decode_token(token)
# Use your actual secret/key and algorithm
JWT.decode(token, "your_secret_key", true, { algorithm: "HS256" }).first
rescue JWT::DecodeError
raise Hanami::InvalidInput, "Invalid token"
end
end
# app/controllers/api/profiles/show.rb
module API
module Profiles
class Show
def call(params)
headers = @request.env["rack.request"] ? @request.env : {} # Hanami provides request env
finder = ProfileFinder.new(headers)
profile = finder.call(params["id"])
{ status: 200, body: { id: profile.id, user_id: profile.user_id, email: profile.email } }
end
end
end
end
2. Use explicit policy checks with a permissions service
Introduce a lightweight policy object that receives the token subject and the target resource, and returns authorized or not. This keeps authorization logic testable and explicit.
# app/policies/profile_policy.rb
class ProfilePolicy
def initialize(user_id, record)
@user_id = user_id
@record = record
end
def show?
@record.user_id == @user_id
end
end
# app/services/profile_policy_service.rb
class ProfilePolicyService
def self.authorize!(profile_id, token)
user_id = decode_token(token)["user_id"]
profile = ProfileRepository.new.find(profile_id)
raise Hanami::Authorization::NotAuthorized unless ProfilePolicy.new(user_id, profile).show?
rescue Hanami::Repository::NoMatchingTuple
raise Hanami::Authorization::NotAuthorized
end
def self.decode_token(token)
JWT.decode(token, "your_secret_key", true, { algorithm: "HS256" }).first
rescue JWT::DecodeError
raise Hanami::Authorization::NotAuthorized
end
end
# app/controllers/api/profiles/show_secure.rb
module API
module Profiles
class ShowSecure
def call(params)
auth_header = @request.env["HTTP_AUTHORIZATION"]
token = auth_header&&.start_with?("Bearer ") ? auth_header.split(" ").last : nil
raise Hanami::Authorization::NotAuthorized unless token
ProfilePolicyService.authorize!(params["id"], token)
profile = ProfileRepository.new.find(params["id"])
{ status: 200, body: { id: profile.id, user_id: profile.user_id, email: profile.email } }
end
end
end
end
3. Validate and scope repository queries
Ensure repository methods always include the subject from the token. Avoid “find by ID only” methods in public-facing contexts; prefer scoped finders.
# app/repositories/profile_repository.rb
class ProfileRepository
def initialize
@db = DB
end
def find_by(id:, user_id:)
@db["profiles"].where(id: id, user_id: user_id).first
end
def all_for_user(user_id)
@db["profiles"].where(user_id: user_id).to_a
end
end
# Usage in an operation or controller
# Ensure you pass the decoded user_id from the token every time
profile = ProfileRepository.new.find_by(id: params["id"], user_id: current_user_id)
4. Secure token handling and error handling
Return generic errors for missing or invalid tokens and avoid exposing internal IDs in error messages. Always use HTTPS to protect Bearer Tokens in transit.
# Example HTTP response for unauthorized requests
# Status: 401 Unauthorized
# Body: { "error": "unauthorized" }
# Do not return stack traces or internal IDs in production.
5. API spec and scanning considerations
Keep your OpenAPI/Swagger spec accurate so middleBrick’s cross-reference checks align spec definitions with runtime behavior. Explicitly document that endpoints require a Bearer Token and describe authorization scopes. This helps automated scans correlate authentication mechanisms with expected authorization rules and surface BOLA findings with severity and remediation guidance.
Related CWEs: bolaAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-250 | Execution with Unnecessary Privileges | HIGH |
| CWE-639 | Insecure Direct Object Reference | CRITICAL |
| CWE-732 | Incorrect Permission Assignment | HIGH |