Skip to content

Domain-Based Access Control for Public and Admin URLs #5887

@mattinannt

Description

@mattinannt

Summary

Implement domain-based access control to separate public survey functionality from admin interface using different domains. This allows organizations to restrict admin access internally while keeping public surveys accessible externally.

Problem Statement

A customer requested the ability to have separate domains for:

  • Admin Domain (survey-admin.example.com): Internal access only, used for dashboard, management APIs, and administrative functions
  • Public Domain (survey-public.example.com): External access allowed, limited to public survey pages and client APIs for SDK integration

The current SURVEY_URL implementation doesn't provide a hard separation between these functionalities.

Solution Overview

Modify the middleware to implement domain-based routing that:

  1. Identifies the requesting domain
  2. Allows/blocks access based on domain and route combination
  3. Maintains full backward compatibility with existing single-domain deployments

Architectural Overview

Public Domain (PUBLIC_URL)

  • Accessible Routes:
    • /s/[surveyId]
    • /c/[jwt]
    • /api/v1/client/**
    • /api/v2/client/**
    • /share/**
    • /health
  • Purpose: Serve public-facing survey pages and client APIs.

Admin Domain (WEBAPP_URL)

  • Accessible Routes:
    • /environments/**
    • /auth/**
    • /setup/**
    • /organizations/**
    • /product/**
    • /api/v1/management/**
    • /api/v2/management/**
    • /health
    • / (root)
  • Purpose: Provide access to administrative dashboards and management APIs.

Exceptions & Notes

  • Static assets (e.g., /_next/static, /favicon.ico) must be served on both domains.
  • The /health endpoint should be available on both domains to support monitoring from both internal and external sources.
  • Only routes passing through the middleware can be restricted; any uncategorized routes (such as cron or pipeline jobs) should default to being available only on the admin domain.

Task Breakdown

The following tasks (each tracked as a sub-issue) are all required to ensure robust domain-based access control.

1. Environment Variable Configuration(#5965)

To enable domain-based access control, we must introduce the PUBLIC_URL environment variable and remove the deprecated SURVEY_URL. This change forms the foundation for all subsequent routing and separation logic, ensuring the app knows which domain is public and which is for admins.

2. Middleware Implementation(#5966)

The middleware is the core logic enforcing the domain-based separation. It routes requests to either the public or admin functionality based on the request’s domain, blocking access where appropriate and ensuring a strict boundary between public and internal features.

3. API Endpoint Reorganization(#5962)

API endpoint /api/v1/og needs to be moved under the client API namespace to match the new public/admin split. This ensures that only the intended APIs are exposed on each domain, keeping the separation clean and predictable.

4. Move avatar uploads to private storage(#5980)

Update: We will remove this functionality soon, please use the public_domain for the avatar assets for now.

We are moving the path of public assets(uploads) to the public_domain, to keep the separation between public and private assets. We store the avatar images in the public directory, but the avatars should not be publicly accessible and can be moved to private storage for consistency.

5. Documentation (#5968)

Update the documentation to reflect the new domain-based access control setup and add a section on how to configure the domains, and the routes that are available on each domain.

Configuration Examples

Default Setup (Most Customers)

# No changes needed - current behavior maintained
WEBAPP_URL=https://app.formbricks.com
# PUBLIC_URL not set

Domain Separation Setup (New Feature)

# Admin domain (internal access)
WEBAPP_URL=https://survey-admin.example.com

# Public domain (external access) - with enforced route separation
PUBLIC_URL=https://survey-public.example.com

# DNS/Network setup required:
# - survey-admin.example.com → internal/VPN access only
# - survey-public.example.com → public internet access

Testing Strategy

  • Backward Compatibility: Verify that existing single-domain deployments function without changes when PUBLIC_URL is unset.
  • Domain Separation: Test that public and admin domains serve only their respective routes.
  • Error Handling: Ensure that unauthorized access attempts result in appropriate error responses (e.g., 404).

Out of scope

  • We are not modifying the one click setup or basic helm chart configuration as this is a niche feature only used by a very small number of enterprise customers managing their own infrastructure to set up the desired behavior.

Acceptance Criteria

  • When PUBLIC_URL is not set: all routes work on WEBAPP_URL (current behavior)
  • When PUBLIC_URL is set: domain separation is enforced
  • Public domain only serves survey pages (/s/, /c/), client APIs (/api/v*/client/), and share survey results (/share/<id>)
  • Admin domain only serves dashboard routes and management APIs
  • Proper 404 responses for blocked routes on each domain
  • Error handling prevents service disruption
  • All existing functionality works unchanged when PUBLIC_URL is not set
  • Comprehensive test coverage for domain routing logic
  • Documentation updated with clear setup instructions

Metadata

Metadata

Labels

No labels
No labels
No fields configured for Feature.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions