Insecure Design in Hanami with Bearer Tokens
Insecure Design in Hanami with Bearer Tokens — how this specific combination creates or exposes the vulnerability
Insecure design in a Hanami application often arises when authentication decisions are embedded in the application architecture in a way that makes token validation inconsistent or bypassable. When Bearer tokens are used without a robust, centralized authorization strategy, the API surface expands in ways that violate the principle of least privilege.
Hanami, being a web framework that favors explicit routing and modular architecture, can inadvertently expose endpoints if routes are defined without enforcing token validation for sensitive actions. For example, an endpoint like POST /api/v1/admin/users might be implemented in a controller without verifying that the incoming Bearer token has admin-scoped claims. Because Hanami encourages separating concerns, developers might place authentication logic in a separate action or middleware but forget to apply it uniformly across all resource controllers.
Another insecure design pattern involves how token scope is interpreted. If the application trusts the presence of a Bearer token without validating the scope or role encoded within it, an authenticated context is incorrectly treated as an authorized one. This becomes critical when token introspection is not performed against an identity provider or when tokens are issued with broad permissions for convenience during development and never revisited.
Consider an API route defined as:
module Web::Controllers::Admin
class Users
include Web::Action
def call(params)
# Insecure design: No token validation or scope check
user = UserRepository.new.create(params[:user])
Response.new(201, {}, user.to_json)
end
end
end
In this scenario, any request reaching this route with a valid Bearer token in the Authorization header is processed, regardless of whether the token represents an admin user. The insecure design lies in assuming that routing to this controller implies proper authorization, rather than explicitly verifying token claims.
Additionally, Hanami’s support for multiple API versions can compound the risk if older versions of endpoints are not retired or properly secured. An API route like /api/v1/resources might be deprecated, but if it remains accessible and lacks Bearer token enforcement, it becomes a pathway for privilege escalation or data exposure. The framework does not enforce security by default, so the burden falls on the developer to ensure every route requiring protection includes appropriate checks.
Insecure design also manifests in how tokens are stored and transmitted. If a Hanami frontend or client-side JavaScript bundle includes hardcoded Bearer tokens or exposes token endpoints without rate limiting or input validation, it increases the attack surface. Tokens might be leaked through logs, error messages, or browser developer tools when debugging endpoints that lack proper access controls.
Finally, the combination of Hanami’s flexible routing and Bearer token usage without mandatory scope validation can lead to horizontal privilege issues. Two users with similar roles might access each other’s data because the API endpoints do not enforce ownership checks at the design level, relying only on the presence of a token rather than a comprehensive policy model.
Bearer Tokens-Specific Remediation in Hanami — concrete code fixes
To remediate insecure design issues with Bearer tokens in Hanami, implement centralized authorization checks and validate token claims before allowing access to sensitive endpoints. Below are concrete code examples demonstrating secure patterns.
1. Centralized Authentication Action
Create a base action that validates the Bearer token and enforces scope requirements. This ensures consistent security across controllers.
module Web::Actions::Authenticated
def self.included(base)
base.class_eval do
before :authorize_bearer_token
end
end
def authorize_bearer_token
auth_header = request.env['HTTP_AUTHORIZATION']
unless auth_header&.start_with?('Bearer ')
halt 401, {}, { error: 'Unauthorized' }.to_json
end
token = auth_header.split(' ').last
# Validate token with identity provider or introspection endpoint
decoded = validate_token(token)
if decoded.nil?
halt 403, {}, { error: 'Forbidden: Invalid token' }.to_json
end
# Enforce scope for admin routes
if route_settings[:required_scope] && !decoded['scope'].to_s.split.include?(route_settings[:required_scope])
halt 403, {}, { error: 'Insufficient scope' }.to_json
end
@current_user = decoded
end
def validate_token(token)
# Replace with actual validation logic, e.g., call to auth server
# This is a simplified example
return { 'scope' => 'admin read write', 'sub' => 'user-123' } if token == 'valid_admin_token'
return { 'scope' => 'read', 'sub' => 'user-456' } if token == 'valid_read_token'
nil
end
end
2. Applying Authentication to Specific Controllers
Include the authenticated action in controllers that require Bearer token validation and specify required scopes where necessary.
module Web::Controllers::Admin::Users
class Create
include Web::Action
include Web::Actions::Authenticated
# Require admin scope for this endpoint
route_settings required_scope: 'admin'
def call(params)
user = UserRepository.new.create(params[:user])
Response.new(201, {}, user.to_json)
end
end
3. Token Validation Middleware Alternative
For broader protection, implement a middleware that checks Bearer tokens before routing reaches Hanami controllers.
module Web::Middleware::BearerAuth
def initialize(app)
@app = app
end
def call(env)
request = Rack::Request.new(env)
if request.path.start_with?('/api/')
auth_header = request.env['HTTP_AUTHORIZATION']
if auth_header&.start_with?('Bearer ')
token = auth_header.split(' ').last
decoded = validate_token(token)
if decoded
env['warden'] = { user: decoded, scope: decoded['scope'] }
else
return [401, { 'Content-Type' => 'application/json' }, [{ error: 'Invalid token' }.to_json]]
end
else
return [401, { 'Content-Type' => 'application/json' }, [{ error: 'Missing token' }.to_json]]
end
end
@app.call(env)
end
private
def validate_token(token)
# Integration with identity provider
{ 'sub' => 'user-id', 'scope' => 'read write' } if token.length > 10
end
end
# config/initializers/middleware.rb
require_relative 'middleware/bearer_auth'
Rack::Builder.new do
use Web::Middleware::BearerAuth
run Hanami.app
end
4. Scope-Based Authorization in Controller Logic
Even after validating the token, enforce scope checks within business logic to prevent horizontal access violations.
module Web::Controllers::Resources
class Show
include Web::Action
include Web::Actions::Authenticated
def call(params)
resource = ResourceRepository.find(params[:id])
# Ensure user scope aligns with resource ownership or permissions
unless current_user_can_access?(@current_user, resource)
halt 403, {}, { error: 'Access denied to resource' }.to_json
end
Response.new(200, {}, resource.to_json)
end
private
def current_user_can_access?(user, resource)
# Implement policy: users can only access their own resources unless admin
user['scope'].to_s.split.include?('admin') || user['sub'] == resource.user_id
end
end
end
5. Example of a Valid Bearer Token Request
Ensure clients send tokens correctly and that the server validates format strictly.
curl -X GET "http://localhost:2300/api/v1/admin/users" \
-H "Authorization: Bearer valid_admin_token" \
-H "Content-Type: application/json"
6. Rejecting Malformed or Missing Tokens
Return clear error messages without exposing internal details.
def authorize_bearer_token
auth_header = request.env['HTTP_AUTHORIZATION']
unless auth_header&.match?(/^Bearer [A-Za-z0-9\-_=]+\.[A-Za-z0-9\-_=]+/?[A-Za-z0-9\-_.+/=]*$/)
halt 400, {}, { error: 'Malformed authorization header' }.to_json
end
# ... continue validation
end