Cors Wildcard in Rails with Jwt Tokens
Cors Wildcard in Rails with Jwt Tokens — how this specific combination creates or exposes the vulnerability
In a Rails API that uses JWT-based authentication, a CORS configuration with an origin wildcard (origins: '*') can unintentionally expose authenticated routes to any web page. When credentials are allowed alongside a wildcard origin, browsers permit cross-origin requests that include cookies or authorization headers. Because JWTs are often sent in the Authorization: Bearer <token> header, a malicious site can make authenticated requests on behalf of a user if the backend does not validate the origin.
This combination is risky because the same-origin policy is relaxed by the wildcard, but the presence of JWTs in headers signals an authenticated context. An attacker can craft a page that calls your API endpoints, leveraging the permissive CORS rules to perform actions with the victim’s token. Even if the token is not stored in a cookie, the wildcard allows the request to proceed, and the server may treat it as valid because it does not check the Origin header against an allowlist.
For example, a Rails controller using skip_before_action :verify_authenticity_token and relying solely on JWT validation may still process requests from any origin if CORS permits it. Without explicit origin restrictions, an attacker can trigger cross-origin authenticated calls via embedded scripts or forms, leading to unauthorized actions or data access. This pattern is commonly seen in applications that expose user-specific endpoints (e.g., /api/users/me) without origin checks.
Additionally, preflight requests (OPTIONS) may return permissive headers like Access-Control-Allow-Origin: * and Access-Control-Allow-Headers: Authorization, signaling to browsers that cross-origin authenticated requests are acceptable. Once the preflight succeeds, the actual request with the JWT in the header is allowed, bypassing intended isolation between web origins.
Tools like middleBrick can detect such misconfigurations by scanning the API endpoints and inspecting CORS response headers alongside the authentication mechanisms in use. Its checks include identifying wildcard origins when authentication schemes like JWT are present, helping teams understand where their unauthenticated scan reveals risky exposure.
Jwt Tokens-Specific Remediation in Rails — concrete code fixes
To secure a Rails API using JWTs, configure CORS to explicitly specify allowed origins and avoid sending permissive headers for authenticated routes. Below are code examples that demonstrate a safe setup.
1. Configure a strict CORS policy in config/initializers/cors.rb
# config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'https://your-trusted-frontend.com', 'https://app.yourdomain.com'
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head],
expose: ['X-CSRF-Token', 'X-Rate-Limit-Limit', 'X-Rate-Limit-Remaining'],
max_age: 1728000
end
end
Replace the origins with your frontend domains. Avoid '*' when authentication headers are used. The expose list should only include non-sensitive headers that the frontend needs to read.
2. Validate JWT and origin in the application controller
# app/controllers/application_controller.rb
class ApplicationController < ActionController::API
before_action :authenticate_from_jwt
before_action :cors_preflight_check
private
def authenticate_from_jwt
header = request.headers['Authorization']
token = header&.split(' ')&.last if header
return head :unauthorized unless token&;present?
# Example using knock or a custom decoder; adapt to your JWT library
begin
decoded = JWT.decode(token, Rails.application.secrets.secret_key_base, true, { algorithm: 'HS256' })
@current_user = User.find(decoded[0]['user_id'])
rescue JWT::DecodeError, ActiveRecord::RecordNotFound
head :unauthorized
end
end
def cors_preflight_check
if request.method == 'OPTIONS'
headers['Access-Control-Allow-Origin'] = request.headers['Origin'] || ''
headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD'
headers['Access-Control-Allow-Headers'] = 'Authorization, Content-Type'
headers['Access-Control-Max-Age'] = '1728000'
headers['Access-Control-Allow-Credentials'] = 'true'
render plain: '', content_type: 'text/plain'
end
end
end
This ensures that only requests with a valid JWT and an allowed origin are processed. The cors_preflight_check method responds to OPTIONS requests with the requesting origin rather than a wildcard when credentials are involved.
3. Use a gem like rack-cors with request-based configuration
# config/initializers/cors.rb with request-based origin validation
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins ->(request) { request.headers['Origin'] if request.headers['Origin'] =~ /\.yourdomain\.com$/ }
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head],
max_age: 1728000
end
end
This dynamic approach allows origins that match your domain pattern while rejecting unknown sources. Combined with JWT validation in the controller, it reduces the risk of cross-origin abuse.
Using middleBrick’s scans, teams can verify that CORS headers do not expose Access-Control-Allow-Origin: * when authentication mechanisms like JWT are active. The tool surfaces per-category findings and provides prioritized remediation guidance aligned with frameworks such as OWASP API Top 10.
Related CWEs: dataExposure
| CWE ID | Name | Severity |
|---|---|---|
| CWE-200 | Exposure of Sensitive Information | HIGH |
| CWE-209 | Error Information Disclosure | MEDIUM |
| CWE-213 | Exposure of Sensitive Information Due to Incompatible Policies | HIGH |
| CWE-215 | Insertion of Sensitive Information Into Debugging Code | MEDIUM |
| CWE-312 | Cleartext Storage of Sensitive Information | HIGH |
| CWE-359 | Exposure of Private Personal Information (PII) | HIGH |
| CWE-522 | Insufficiently Protected Credentials | CRITICAL |
| CWE-532 | Insertion of Sensitive Information into Log File | MEDIUM |
| CWE-538 | Insertion of Sensitive Information into Externally-Accessible File | HIGH |
| CWE-540 | Inclusion of Sensitive Information in Source Code | HIGH |