Give nginx back the query string on routed requests - #1163
Merged
Conversation
A request for anything that is not a real file is rewritten to api/index.php. Apache does that with [QSA,L] and carries the query string. nginx did it with a try_files fallback naming a plain URI, and a plain URI hands the router an EMPTY query string -- so on nginx every routed endpoint that reads a query parameter silently saw nothing. Route::queryParam() has been working around this since GH-529: it re-parses REQUEST_URI when $_GET is empty, which is why the API's expand, start and length still worked. Nothing outside Route could, because queryParam() was protected. That is the whole of the OIDC plugin on nginx. Its login button links to /ext/oidc/start?provider=3; OIDCFlow reads the id with filter_input(INPUT_GET, 'provider'), gets null, and refuses a configured and enabled provider with "Unknown identity provider". Its callback reads state, code and error the same way, so the flow could not have completed either. Measured on the lab server: QUERY_STRING arrived as '' and the id as 0. Two halves, both needed. The installer appends $is_args$args to all three nginx vhost writers, which fixes it at the source -- but only for a server that re-runs the installer, and an existing vhost is not rewritten by anything. So queryParam() keeps its REQUEST_URI fallback and becomes public, because a plugin registering a handler on this router hits exactly the same wall and should not have to re-invent the answer. Apache is untouched; it was always correct. tests/routed-query-string.test.php counts the fallbacks rather than matching one, so a fourth vhost writer added without the suffix fails here. Five mutations verified, all caught. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017aBSWrDArXHTpKWkkN27LR
Merged
mastacontrola
added a commit
that referenced
this pull request
Aug 18, 2026
Carries FOGProject/fog-plugins#14: the OIDC flow reads its query parameters through Route::queryParam(), which #1163 made public. Without this bump the fix cannot reach a server -- fetch-plugins.sh downloads whatever this constant names. Claude-Session: https://claude.ai/code/session_017aBSWrDArXHTpKWkkN27LR Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
Anything that is not a real file is served through an internal rewrite to
api/index.php. Apache does that with[QSA,L]and carries the query string.nginx did it with a
try_filesfallback naming a plain URI — and a plain URIhands the router an empty query string.
So on nginx, every routed endpoint that reads a query parameter silently saw
nothing. Measured on the lab server before the fix:
Route::queryParam()has been working around this since #529 by re-parsingREQUEST_URI, which is why the API'sexpand/start/lengthstill worked.Nothing outside
Routecould — the method wasprotected.What it cost
The whole OIDC plugin, on every nginx install. Its login button links to
/ext/oidc/start?provider=3;OIDCFlowread the id withfilter_input(INPUT_GET, 'provider'), gotnull, and refused a configured,enabled provider with "Unknown identity provider".
callback()readsstate,codeanderrorthe same way, so the flow could not have completed either.The fix — two halves, both needed
$is_args$argsto all three nginx vhost writersRoute::queryParam()keeps itsREQUEST_URIfallback and becomes publicMaking it public is the point of the second half: a plugin registering a handler
on this same router hits exactly the same wall, and the working answer should be
reachable rather than re-invented. The OIDC plugin's
?provider=is how this wasfound; the companion fix is FOGProject/fog-plugins#14.
Apache is untouched — it was always correct, and the test now pins that too.
Verification
Live on the lab server. Before the vhost fix,
/fog/ext/oidc/start?provider=3302'd straight back to the login page with "Unknown identity provider". After:
and a full sign-in against a real Keycloak now completes end to end.
tests/routed-query-string.test.phpcounts the fallbacks rather than matchingone, so a fourth vhost writer added without the suffix fails here — same for the
three Apache rewrites and their
QSA. Five mutations verified, all caught.sh tests/run-all.sh→ 51 passed, 0 failed.Downstream
None. No route class list change, no schema change, no OpenAPI change.
🤖 Generated with Claude Code
https://claude.ai/code/session_017aBSWrDArXHTpKWkkN27LR