Why does my OAuth sign-in button do nothing when clicked?
The content security policy is blocking the navigation. form-action covers the whole redirect chain a form starts, so the provider's host must be listed in config/initializers/content_security_policy.rb. The form also needs data-turbo="false", or Turbo submits it by fetch and the cross-origin redirect is blocked as an XHR instead.
Why there is no error
This is the part that wastes the afternoon. A blocked form action produces no exception, no flash, no failed request in the network tab and nothing in the Rails log. The browser simply declines to navigate. The only evidence is a console warning, which is easy to miss if the console is noisy.
Both conditions have to hold
The host has to be in form-action. Not connect-src, not default-src. form-action applies
to the entire navigation the form begins, including every redirect along the way, so an OAuth flow
that bounces through two hosts needs both listed.
The form needs data-turbo="false". Turbo checks the form and the submit button, so either works.
Without it, Turbo intercepts the submission and issues a fetch, which turns a navigation into an
XHR, which connect-src then blocks. Same dead button, different rule.
Fix and pin
bin/rspec spec/requests/content_security_policy_spec.rb spec/requests/cross_origin_forms_spec.rb
Both specs exist so this failure cannot come back silently. Add the host to
config/initializers/content_security_policy.rb, add the example to
spec/requests/content_security_policy_spec.rb, and the next person who tightens the policy gets a
red build instead of a broken sign-in button.
The same trap, three other places
Any redirect off-site has this shape. Stripe's hosted checkout, the Stripe billing portal, and any
third-party consent screen all need the host in form-action and the form marked
data-turbo="false". If one of those buttons stops working after a policy change, this is why.
Check the console before anything else
Open devtools, click the button, and read the one console line. It names the directive and the blocked URL, which turns a guess into a two-minute fix. That line is the entire diagnostic, and it is the only place the failure is visible.