Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,13 @@ public function handle($request, Closure $next)
);
throw new InvalidGrantTypeException(OAuth2Protocol::OAuth2Protocol_Error_InvalidToken);
}
$allowedOrigins = array_filter(array_map(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mulldug
Move the array build inside the JS_CLIENT branch so server-to-server tokens do not pay the cost

fn($o) => rtrim(trim($o), '/'),
explode(' ', $token_info->getAllowedOrigins() ?? '')
));
if (
$token_info->getApplicationType() === 'JS_CLIENT'
&& (is_null($origin) || empty($origin)|| str_contains($token_info->getAllowedOrigins(), $origin) === false )
&& (is_null($origin) || empty($origin) || !in_array(rtrim($origin, '/'), $allowedOrigins, true))
Copy link
Copy Markdown
Collaborator

@smarcet smarcet May 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mulldug
Asymmetric normalization — risk of false 403s for legitimate JS clients

$origin is built by RequestUtils::getOrigin(), which runs the header through URL\Normalizer (lowercases scheme/host, strips default ports :80/:443, canonicalizes percent-encoding, resolves dot segments, may add/strip trailing /).

$allowedOrigins here only gets trim() + rtrim('/') — no host-lowercasing, no port normalization, no percent-encoding canonicalization.

Combined with strict in_array(..., true) (byte-exact, case-sensitive), any IDP record stored as e.g. https://Example.com, https://example.com:443, or with percent-encoded chars will silently start returning 403 unauthorized_client after this ships — even though the old str_contains accidentally tolerated those mismatches.

Failure mode is silent and uniform across affected clients, and local tests won't catch it (fixtures use allowed_origins => '', so the JS_CLIENT branch is skipped).

Suggested fix: normalize each entry through the same URL\Normalizer before the strict compare, so both sides come out of the same pipeline:

$allowedOrigins = array_filter(array_map(function ($o) {
    $o = trim($o);
    if ($o === '') return '';
    try { $o = (new \URL\Normalizer($o))->normalize(); } catch (\Throwable $e) {}
    return rtrim($o, '/');
}, explode(' ', $token_info->getAllowedOrigins() ?? '')));

Recommended pre-rollout check: query the token store for allowed_origins entries containing uppercase letters, :80/:443, or %-encoding to surface clients at risk.

) {
//check origins
throw new OAuth2ResourceServerException(
Expand Down
Loading