From 0c440b29017177cc11881c1f11f9bec1df0db7b2 Mon Sep 17 00:00:00 2001 From: Ali Hamdi Ali Fadel Date: Fri, 10 Jul 2026 23:13:34 +0300 Subject: [PATCH] fix(oauth): use native consent form navigation --- app/views/doorkeeper/authorizations/new.html.erb | 9 +++++++-- test/integration/oauth_authorization_flow_test.rb | 5 +++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/views/doorkeeper/authorizations/new.html.erb b/app/views/doorkeeper/authorizations/new.html.erb index 755740f..ecd7dc8 100644 --- a/app/views/doorkeeper/authorizations/new.html.erb +++ b/app/views/doorkeeper/authorizations/new.html.erb @@ -50,12 +50,17 @@ CANONICAL McpOauth::CONFIG[:resource_uri], never the client's spelling -- which Doorkeeper's stock form would silently drop (RFC 8707 round-trip). %> - <%= form_tag oauth_authorization_path, method: :post do %> + <%# OAuth completes with a cross-origin redirect to the client's callback + (commonly http://localhost: for CLI agents). Turbo would follow + that redirect as fetch(), which the callback correctly rejects via + CORS, leaving this page apparently stuck. Native form navigation is + required for both approval and denial. %> + <%= form_tag oauth_authorization_path, method: :post, data: { turbo: false } do %> <%= render "authorization_params" %> <%= submit_tag t(".authorize"), class: "w-full rounded-md border-3 border-ink bg-hero-red px-4 py-3 font-display text-2xl tracking-wide text-white shadow-comic-sm hover:bg-hero-red/90 t-press" %> <% end %> - <%= form_tag oauth_authorization_path, method: :delete do %> + <%= form_tag oauth_authorization_path, method: :delete, data: { turbo: false } do %> <%= render "authorization_params" %> <%= submit_tag t(".deny"), class: "w-full rounded-md border-2 border-ink bg-white px-4 py-2 font-display text-xl tracking-wide text-ink shadow-comic-sm hover:bg-paper t-press" %> diff --git a/test/integration/oauth_authorization_flow_test.rb b/test/integration/oauth_authorization_flow_test.rb index 3184deb..f40b060 100644 --- a/test/integration/oauth_authorization_flow_test.rb +++ b/test/integration/oauth_authorization_flow_test.rb @@ -20,6 +20,11 @@ class OauthAuthorizationFlowTest < ActionDispatch::IntegrationTest assert_select "input[type=hidden][name=code_challenge][value=?]", code_challenge assert_includes response.body, client.name + # Approval and denial redirect to a client-controlled callback origin + # (usually localhost for CLI agents). Turbo must not turn those redirects + # into cross-origin fetches; both forms require native browser navigation. + assert_select "form[data-turbo='false']", count: 2 + # Requested scopes are shown with human labels. assert_includes response.body, I18n.t("doorkeeper.scopes.mcp:read") assert_includes response.body, I18n.t("doorkeeper.scopes.mcp:write")