Add AUTH=TOKEN, a server-declared API route marker - #192
Conversation
An API route wants to accept Basic credentials without advertising them. The distinction matters because a WWW-Authenticate header makes a browser pop its native credential dialog, and the Basic credentials it caches afterwards outlive the token session -- so logging out of the application stops ending anything (#119). #120 fixed the immediate symptom by suppressing the challenge when a request carries X-CSRF-ZOSMF-HEADER. That works because our SPA and Zowe always send it, but it is a guess made from the request rather than a property the operator declared, and real z/OSMF does not use the header at all. AUTH=TOKEN is the declared form: the route answers a bare 401, unconditionally, without consulting the request. #120's heuristic stays where the issue put it, as an additional suppressor for BASIC and inherited routes. Not the AUTH=BASIC,TOKEN comma list that #98 documented but never built. That spelling reads as a list of accepted credential sources, and the source axis does not exist per route: resolve_credential() runs in httppc() before the route is even looked up, so every route already accepts the Sec-Token and LtpaToken2 cookies, Bearer and Basic. AUTH= only ever selected whether a login is required and how a missing one is challenged, so the honest fix is a fourth challenge mode -- and TOKEN as a "source" would have been meaningless anyway, since every route takes tokens today. TOKEN lands on the authentication-required side of the stage-1 test, like BASIC, so marking a route with it does not make it public. Both route-table decoders learn the name, since a bare 4 in ?target=MOD or ?debug=cgi would cost exactly the reader who is debugging an auth problem. Left out deliberately: the issue's fourth bullet, whether XHR clients hitting a FORM or inherited route should get a 401 instead of the login form. That would change behaviour for routes nobody marked, which is the opposite of what this change is for. Fixes #121
Verified on a live serverParmlib under test: The route table decodes the new mode
The discriminating case
The first row is the one that matters: without the CSRF marker, #120's heuristic would have emitted the challenge. The declared marker suppresses it regardless of what the client sent, which is the whole point — the two rows being identical is the evidence that The third row confirms the other half: Also confirmed in the same restart
Note on the bannerThe running server reports |
Fixes #121.
The decision the issue left open
The issue offered two spellings — resurrect
AUTH=BASIC,TOKENfrom #98, or add a modeAUTH=TOKEN— and deferred the choice to design. One observation settles it:The credential is resolved before the route is even looked up. Every route already accepts every source —
Sec-Token,LtpaToken2,Bearer,Basic— and no route can restrict that. So the source axis does not exist per route, andAUTH=never selected one: it selects whether a login is required and how a missing one is challenged.That makes
AUTH=BASIC,TOKENthe wrong shape twice over. It reads as a list of accepted sources, which is the conflation this issue exists to remove, andTOKEN-as-a-source would be meaningless because every route takes tokens today. A fourth challenge mode is the honest fix. Nothing depends on the #98 spelling — it was documented but never built.What
AUTH=TOKENdoesA bare
401, unconditionally, without consulting the request. It does not change which credentials are accepted:curl -u user:passagainst aTOKENroute authenticates exactly as against aBASICone — the route just never advertises that it would.#120's
X-CSRF-ZOSMF-HEADERsuppression stays exactly where the issue put it, as an additional suppressor forBASICand inherited routes.TOKENdoes not consult that header at all: an operator declared this route machine-facing, and that must not depend on what a client happens to send. Two heuristics would be worse than one.Why an API route must not challenge
A
WWW-Authenticateheader makes a browser pop its native credential dialog, and the Basic credentials it caches afterwards outlive the token session — every later request carries them, so logging out of the application ends nothing (#119). A bare401leaves the client's own "session expired" handling in charge.Details worth a look
TOKENlands on the authentication-required side of the stage-1 test inauth_gate(), likeBASIC— marking a route with it must not make it public.httpdsrv.c?target=MOD,httpdbug.c?debug=cgi). A bare4there would cost exactly the person debugging an auth problem.samplib/httpprm0documents source-vs-challenge and marks the protected-API exampleAUTH=TOKEN.Deliberately out of scope
The issue's fourth bullet — should XHR clients hitting a
FORMor inherited route get a401instead of the HTML form? That changes behaviour for routes nobody marked, which is the opposite of a server-declared marker. Worth its own issue if wanted.Verification
make modulesclean,make test-host87 assertions. The behaviour is pipeline + route table, so no unit test reaches it; the discriminating live check is aAUTH=TOKENroute answering a bare401without the CSRF header present — the case today's heuristic gets wrong. Pending a Parmlib line and a restart on the test system.