How to Configure Embeddable Property Widgets with Domain Restrictions in PropertyWebBuilder
PropertyWebBuilder lets you restrict widget embedding to specific domains by populating the allowed_domains array on a Pwb::WidgetConfig model, which the public API controller validates against the request's Origin header before serving widget data.
PropertyWebBuilder is an open-source real estate CMS that supports embeddable property listing widgets for external websites. To prevent unauthorized use of these widgets on competitor or malicious sites, the codebase implements a domain restriction system using PostgreSQL arrays and request origin validation. This guide explains how to configure and enforce these restrictions using the actual implementation found in the etewiah/property_web_builder repository.
Understanding the Domain Restriction Architecture
The domain restriction feature operates across three layers: the database model that stores allowed domains, the admin interface that captures user input, and the public API that validates incoming requests.
The WidgetConfig Model and Database Schema
In app/models/pwb/widget_config.rb, the allowed_domains attribute is defined as a PostgreSQL array column. The migration at db/migrate/20251228100000_create_pwb_widget_configs.rb (line 54) declares this as string[] with a default empty array, as reflected in db/schema.rb (line 1502). An empty array signifies that the widget allows embedding from any domain, while a populated array enforces the restriction list.
The Public API Controller Validation
Every public widget request routes through app/controllers/api_public/v1/widgets_controller.rb. Before returning data, the validate_origin method (lines 84-90) extracts the Origin or Referer header, parses the host, and invokes domain_allowed? on the widget configuration instance. If the domain is not allowed, the controller logs a warning (lines 92-95) and can optionally reject the request.
Configuring Allowed Domains
You can populate the domain restriction list either through the administrative interface or programmatically via the Rails console.
Via the Admin Interface
In the Widget edit screen, administrators enter one domain per line in a textarea. The SiteAdmin::WidgetsController processes this input at lines 80-82:
# app/controllers/site_admin/widgets_controller.rb
if permitted[:allowed_domains].is_a?(String)
permitted[:allowed_domains] = permitted[:allowed_domains]
.split("\n")
.map(&:strip)
.reject(&:blank?)
end
This transformation converts the newline-separated string into a clean array before persistence.
Via Rails Console
For automated provisioning or testing, create restricted widgets programmatically:
# Create a widget restricted to specific partners
widget = Pwb::WidgetConfig.create!(
website: my_website,
name: 'Partner Widget',
allowed_domains: ['example.com', '*.partner.com']
)
How Domain Validation Works
The domain_allowed? method in app/models/pwb/widget_config.rb (lines 35-51) implements the authorization logic with support for wildcard patterns.
Wildcard Pattern Matching
The validation normalizes incoming hosts by converting to lowercase and stripping a leading www. prefix. It then checks against stored patterns using the following rules:
- Exact match:
example.commatches onlyexample.com - Wildcard match:
*.example.commatchessub.example.com,deep.sub.example.com, andexample.comitself (the code checks for exact match after stripping the*.prefix)
This allows flexible partner agreements while maintaining strict security boundaries.
Request Evaluation Flow
When a browser requests widget data from GET /api_public/v1/widgets/:widget_key, the controller executes this validation sequence:
- Extract the
OriginorRefererheader from the request - Parse the host component using URI parsing
- Call
domain_allowed?(host)on the widget configuration - If
false, log the unauthorized access attempt and optionally render a 403 Forbidden response
Embedding and Testing Restrictions
Once configured, embedding the widget requires placing the script tag on an authorized domain.
Authorized Domain Embedding
Place the following HTML on a page served from an allowed domain:
<div id="pwb-widget-abc123"></div>
<script src="https://your-property-site.com/widget.js"
data-widget-id="abc123"
async></script>
The browser sends the parent page's Origin header with the API request, triggering the validation logic.
Handling Unauthorized Requests
Currently, unauthorized requests are logged but not blocked by default. The controller at app/controllers/api_public/v1/widgets_controller.rb includes this safety logging:
Rails.logger.warn "Widget #{ @widget_config.widget_key } accessed from unauthorized domain: #{ domain }"
# render json: { error: 'Origin not allowed' }, status: :forbidden # Enable to actively block
Uncomment the render line to enforce hard rejection of unauthorized domains.
Automated Testing with RSpec
Verify your domain logic using the model specs in spec/models/pwb/widget_config_spec.rb (lines 206-243):
describe '#domain_allowed?' do
let(:widget) { create(:pwb_widget_config, allowed_domains: ['example.com', '*.trusted.com']) }
it { expect(widget.domain_allowed?('example.com')).to be true }
it { expect(widget.domain_allowed?('sub.trusted.com')).to be true }
it { expect(widget.domain_allowed?('other.com')).to be false }
end
Summary
- PropertyWebBuilder stores domain restrictions in the
allowed_domainsPostgreSQL array column onPwb::WidgetConfig - Admin configuration accepts newline-separated domains in a textarea, converted to an array by
site_admin/widgets_controller.rb - Validation occurs in
api_public/v1/widgets_controller.rbvia thevalidate_originmethod before serving widget data - Wildcard support allows patterns like
*.partner.comto match subdomains automatically - Empty arrays implicitly allow all domains, while populated arrays enforce explicit whitelists
Frequently Asked Questions
What database type is required for the allowed_domains array?
The allowed_domains column requires PostgreSQL and is defined as a string[] array type. The migration 20251228100000_create_pwb_widget_configs.rb explicitly uses PostgreSQL array syntax, and db/schema.rb confirms this structure. Other database adapters would require modifying the storage strategy to serialize the list as JSON or a delimited string.
Does PropertyWebBuilder support wildcard domains in restrictions?
Yes, the domain_allowed? method in app/models/pwb/widget_config.rb supports wildcard patterns such as *.example.com. When evaluating a request, the code normalizes the incoming host and checks it against both exact matches and wildcard patterns by stripping the leading *. and verifying subdomain relationships.
How does the widget handle requests from unauthorized domains?
By default, the public API controller logs a warning message identifying the unauthorized domain and widget key, but still serves the request. You can enable active blocking by uncommenting the render json: { error: 'Origin not allowed' }, status: :forbidden line in app/controllers/api_public/v1/widgets_controller.rb to return a 403 status instead.
Can I restrict a widget to multiple specific partner domains?
Yes, the allowed_domains attribute accepts an array of strings, allowing you to whitelist multiple exact domains or wildcard patterns simultaneously. For example, ['example.com', '*.partner-a.com', '*.partner-b.com'] permits embedding on the main site and any subdomain of two different partners.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →