<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Anthony Franco Dev]]></title><description><![CDATA[Cofounder at Better Cater. Writing about Rails, AI-assisted development, and building high quality SaaS applications.]]></description><link>https://anthonyfranco.dev</link><generator>RSS for Node</generator><lastBuildDate>Thu, 16 Apr 2026 17:48:27 GMT</lastBuildDate><atom:link href="https://anthonyfranco.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Rails `allowed_redirect_hosts` Finally Fixed Subdomain Redirects]]></title><description><![CDATA[The Problem Everyone Has
You're building a multi-tenant SaaS with subdomains. User logs in at www.myapp.com, you redirect them to tenant1.myapp.com. Rails throws this at you:
ActionController::Redirecting::UnsafeRedirectError:
Unsafe redirect to "htt...]]></description><link>https://anthonyfranco.dev/rails-allowedredirecthosts-finally-fixed-subdomain-redirects</link><guid isPermaLink="true">https://anthonyfranco.dev/rails-allowedredirecthosts-finally-fixed-subdomain-redirects</guid><category><![CDATA[Ruby on Rails]]></category><category><![CDATA[#multitenancy]]></category><dc:creator><![CDATA[Anthony Franco]]></dc:creator><pubDate>Mon, 06 Oct 2025 10:36:05 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-the-problem-everyone-has">The Problem Everyone Has</h2>
<p>You're building a multi-tenant SaaS with subdomains. User logs in at <code>www.myapp.com</code>, you redirect them to <code>tenant1.myapp.com</code>. Rails throws this at you:</p>
<pre><code class="lang-plaintext">ActionController::Redirecting::UnsafeRedirectError:
Unsafe redirect to "http://tenant1.myapp.com"
</code></pre>
<p>Rails 7.0+ blocks cross-host redirects by default (good security!), but it treats your own subdomains as "external" hosts.</p>
<p>So what do you do?</p>
<h2 id="heading-what-weve-all-been-doing-its-bad">What We've All Been Doing (It's Bad)</h2>
<p><strong>Option 1:</strong> Sprinkle <code>allow_other_host: true</code> everywhere</p>
<pre><code class="lang-ruby">redirect_to root_url(<span class="hljs-symbol">subdomain:</span> account.subdomain), <span class="hljs-symbol">allow_other_host:</span> <span class="hljs-literal">true</span>
</code></pre>
<p>Problem: This allows redirects to <strong>any</strong> host, not just your subdomains. You're basically disabling the security feature you wanted.</p>
<p><strong>Option 2:</strong> Monkey-patch Rails core</p>
<pre><code class="lang-ruby"><span class="hljs-comment"># config/initializers/url_host_allowed_patch.rb</span>
<span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">ActionController::Redirecting</span></span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_url_host_allowed?</span><span class="hljs-params">(url)</span></span>
    <span class="hljs-comment"># ... custom logic to check same base domain ...</span>
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>This is what we were doing. It works, but you're overriding Rails internals. It's fragile and breaks with updates.</p>
<h2 id="heading-the-fix-its-stupidly-simple">The Fix (It's Stupidly Simple)</h2>
<p>Rails merged <a target="_blank" href="https://github.com/rails/rails/pull/55420">PR #55420</a> in July 2025. Add this to your config:</p>
<pre><code class="lang-ruby"><span class="hljs-comment"># config/application.rb</span>
config.action_controller.allowed_redirect_hosts = [<span class="hljs-string">".myapp.com"</span>]
</code></pre>
<p>That's it. The dot prefix matches all subdomains: <code>www.myapp.com</code>, <code>tenant1.myapp.com</code>, <code>api.myapp.com</code>.</p>
<p>Now your redirects just work:</p>
<pre><code class="lang-ruby">redirect_to root_url(<span class="hljs-symbol">subdomain:</span> account.subdomain)  <span class="hljs-comment"># Works!</span>
</code></pre>
<p>No <code>allow_other_host: true</code>. No monkey-patches.</p>
<h2 id="heading-pattern-matching-you-get">Pattern Matching You Get</h2>
<p>The feature uses <code>ActionDispatch::HostAuthorization::Permissions</code> under the hood, which supports:</p>
<pre><code class="lang-ruby">config.action_controller.allowed_redirect_hosts = [
  <span class="hljs-string">"api.myapp.com"</span>,           <span class="hljs-comment"># Exact match</span>
  <span class="hljs-string">".myapp.com"</span>,              <span class="hljs-comment"># All subdomains</span>
  /.*\.staging\.myapp\.com/, <span class="hljs-comment"># Regex patterns</span>
  IPAddr.new(<span class="hljs-string">"192.168.0.0/24"</span>) <span class="hljs-comment"># IP ranges</span>
]
</code></pre>
<p>For most multi-tenant apps, you just need the dot prefix.</p>
<p>I hope this helps, it's a brand new feature with almost no documentation yet, so I figured I'd share what I learned.</p>
]]></content:encoded></item></channel></rss>