From 462b4bc0da6770d2433d88dbb249d265375014d0 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 9 Sep 2026 20:04:24 +0930 Subject: [PATCH] Detect OOB oauth client when redirect_uri omitted on authorize --- .../OAuth/OobAuthorizationController.php | 20 +++++- tests/Unit/OobAuthorizationTest.php | 71 +++++++++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 tests/Unit/OobAuthorizationTest.php diff --git a/app/Http/Controllers/OAuth/OobAuthorizationController.php b/app/Http/Controllers/OAuth/OobAuthorizationController.php index 65dff7a98..4a3d826e8 100644 --- a/app/Http/Controllers/OAuth/OobAuthorizationController.php +++ b/app/Http/Controllers/OAuth/OobAuthorizationController.php @@ -43,7 +43,25 @@ class OobAuthorizationController extends ApproveAuthorizationController */ protected function isOutOfBandRequest($authRequest) { - return $authRequest->getRedirectUri() === 'urn:ietf:wg:oauth:2.0:oob'; + $redirectUri = $authRequest->getRedirectUri(); + + if ($redirectUri === 'urn:ietf:wg:oauth:2.0:oob') { + return true; + } + + // RFC 6749 §3.1.2.3 permits a client with a single registered redirect + // URI to omit redirect_uri on the authorize request, in which case the + // league server leaves the auth request's redirect URI null. Fall back + // to the client's registered redirect URIs to still detect an OOB-only + // client. Passport's client entity types getRedirectUri() as string|array. + if ($redirectUri === null) { + $registered = $authRequest->getClient()->getRedirectUri(); + $registered = is_array($registered) ? $registered : [$registered]; + + return count($registered) === 1 && $registered[0] === 'urn:ietf:wg:oauth:2.0:oob'; + } + + return false; } /** diff --git a/tests/Unit/OobAuthorizationTest.php b/tests/Unit/OobAuthorizationTest.php new file mode 100644 index 000000000..6c245cc89 --- /dev/null +++ b/tests/Unit/OobAuthorizationTest.php @@ -0,0 +1,71 @@ +setAccessible(true); + + return $m->invoke($controller, $req); + } + + private function authRequest(?string $redirectUri, array $registered): AuthorizationRequest + { + $client = new Client('cid', 'Test App', $registered); + $req = new AuthorizationRequest; + $req->setClient($client); + $req->setRedirectUri($redirectUri); + + return $req; + } + + #[Test] + public function it_detects_oob_when_redirect_uri_is_explicitly_set(): void + { + $req = $this->authRequest('urn:ietf:wg:oauth:2.0:oob', ['urn:ietf:wg:oauth:2.0:oob']); + + $this->assertTrue($this->isOob($req)); + } + + #[Test] + public function it_detects_oob_when_redirect_uri_is_omitted_for_oob_only_client(): void + { + // redirect_uri omitted -> null on the auth request; client registered + // only the OOB URI. + $req = $this->authRequest(null, ['urn:ietf:wg:oauth:2.0:oob']); + + $this->assertTrue($this->isOob($req)); + } + + #[Test] + public function it_does_not_treat_a_normal_http_client_as_oob(): void + { + $req = $this->authRequest('https://app.example/callback', ['https://app.example/callback']); + + $this->assertFalse($this->isOob($req)); + } + + #[Test] + public function it_does_not_treat_an_omitted_redirect_for_a_multi_uri_client_as_oob(): void + { + $req = $this->authRequest(null, ['urn:ietf:wg:oauth:2.0:oob', 'https://app.example/callback']); + + $this->assertFalse($this->isOob($req)); + } +}