Skip to content

fix: read Authorization via getallheaders() when Apache withholds it from $_SERVER - #24

Merged
tomasstark merged 2 commits into
mainfrom
fix/apache-authorization-header
Jul 14, 2026
Merged

fix: read Authorization via getallheaders() when Apache withholds it from $_SERVER#24
tomasstark merged 2 commits into
mainfrom
fix/apache-authorization-header

Conversation

@tomasstark

Copy link
Copy Markdown
Contributor

This PR makes RequestContext::fromGlobals() fall back to getallheaders() for the Authorization header, fixing silently-broken verification on Apache with mod_php.

Context

Apache/mod_php withholds the Authorization request header from the CGI-style $_SERVER variables (a CGI-era security behavior), while getallheaders() still exposes it. fromGlobals() read only $_SERVER['HTTP_AUTHORIZATION'] / REDIRECT_HTTP_AUTHORIZATION, so on vanilla Apache — one of the most common PHP deployments — every bearer looked absent. Because verification fails closed, nothing errored: status probes got the 404 {"supertab": true} decoy and license-token checks treated licensed bots as unlicensed.

Found live: the self-report demo site (#23) deployed on php:8.3-apache returned only the decoy to backend health probes. Empirically confirmed in the container — getallheaders() carried the challenge JWT while $_SERVER had nothing. The demo image works around it with SetEnvIf; this PR fixes the SDK so integrators don't need to know the incantation.

Key Changes

  • New pure helper RequestContext::resolveAuthorizationHeader(array $server, array $requestHeaders): ?string$_SERVER still wins; otherwise the raw request headers are searched case-insensitively for Authorization. fromGlobals() feeds it getallheaders() when the SAPI provides the function (guarded by function_exists; the FPM and built-in-server SAPIs have it too, plain CLI doesn't).
  • 5 unit tests covering precedence, both fallbacks, case-insensitive matching, and the absent-everywhere null.

Verified on real Apache: ran the demo container with its SetEnvIf workaround removed and this patch applied — $_SERVER['HTTP_AUTHORIZATION'] unset, fromGlobals()->authorizationHeader correctly returns the bearer. Full suite green (286 tests).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR updates RequestContext::fromGlobals() to correctly resolve the Authorization header on Apache/mod_php deployments by falling back to raw request headers (getallheaders()) when CGI-style $_SERVER variables don’t include it.

Changes:

  • Added RequestContext::resolveAuthorizationHeader() to centralize Authorization resolution with sensible precedence.
  • Updated fromGlobals() to use the helper and optionally read getallheaders().
  • Added unit tests covering precedence, fallbacks, case-insensitive header name matching, and null behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/Http/RequestContext.php Adds Authorization resolution helper and uses getallheaders() as a fallback in fromGlobals().
tests/Http/RequestContextTest.php Adds focused unit tests for the new Authorization resolution behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Http/RequestContext.php
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@tomasstark tomasstark changed the title Read Authorization via getallheaders() when Apache withholds it from $_SERVER fix: read Authorization via getallheaders() when Apache withholds it from $_SERVER Jul 14, 2026
@tomasstark
tomasstark merged commit 1864899 into main Jul 14, 2026
5 checks passed
@tomasstark
tomasstark deleted the fix/apache-authorization-header branch July 14, 2026 12:42
tomasstark added a commit that referenced this pull request Jul 14, 2026
This PR adds `demo/self-report/` — a minimal vanilla-PHP publisher for
testing the `/.well-known/supertab/status` self-report endpoint
end-to-end against the real sandbox API, deployed as a persistent public
target on **Fly.io** (hostname kept internal; see the sandbox
merchant-site registration). It doubles as the canonical "plain PHP"
integration reference: every request flows through
`SupertabConnect::handleRequest()`, so the status endpoint works with
zero endpoint-specific code.

## Context

The backend's live-health probe needs a real, registered origin to mint
challenges against (`aud` = origin, signed by the platform JWKS). The
existing `demo/` CLI demo is self-contained with a mock API, so it can't
exercise this. This app pins the **released Packagist SDK**
(`1.4.0-beta.9`, which ships the `component: {kind: "php-sdk"}`
identity) — validating a new release is a pin bump + `fly deploy`.

## Key Changes

- `index.php` (~100 lines, no framework): `X-Forwarded-Proto` scheme
fix-up (TLS-terminating proxies forward plain HTTP; without it every
probe's `aud` mismatches and gets the decoy) → `/healthz` short-circuit
(health checks never look like traffic) → everything else through
`handleRequest()`, emitting `Allow` as a demo HTML page and
`Block`/`Respond` verbatim.
- Env-var config: `SUPERTAB_MERCHANT_API_KEY` (required, fail-fast 500),
`SUPERTAB_BASE_URL` (defaults sandbox), `SUPERTAB_ENFORCEMENT`,
`SUPERTAB_ANALYTICS` — so the payload's `enforcement`/`eventReporting`
can be flipped per test without code changes.
- `Dockerfile`: multi-stage composer install from the committed
lockfile; `php:8.3-apache` on port 8080 with `FallbackResource
/index.php` (dot-prefixed paths route to the front controller without
mod_rewrite) and a `SetEnvIf` restoring the `Authorization` header,
which Apache/mod_php withholds from `$_SERVER` (root fix in #24).
- `fly.toml`: one always-warm machine (`auto_stop_machines = 'off'`,
`min_machines_running = 1`) so backend probes never hit a cold start;
HTTP health check on `/healthz`.
- `DEPLOY.md`/`README.md`: Fly runbook — setup, smoke checks, sandbox
registration, update cadence.

Verified end-to-end: the deployed instance passed the backend's live
self-report check — challenge verified against the sandbox platform
JWKS, `200` payload with `component: {kind: "php-sdk", version:
"v1.4.0-beta.9"}`. Along the way this rig surfaced the Apache
`Authorization`-header bug now fixed in #24.
tomasstark added a commit to getsupertab/connect-wp that referenced this pull request Jul 14, 2026
…Status_Handler (#19)

This PR fixes the self-report status endpoint silently serving the 404
decoy to the backend's challenge probes on hosts where Apache withholds
the `Authorization` header from `$_SERVER` — which is why the live
self-report check fails for sites on WP Engine despite running
1.3.0-beta.8.

## Context

The backend's health check probes `/.well-known/supertab/status` with a
challenge JWT in the `Authorization` header.
`Status_Handler::get_authorization_header()` read only
`$_SERVER['HTTP_AUTHORIZATION']` / `REDIRECT_HTTP_AUTHORIZATION` — but
Apache withholds `Authorization` from the CGI-style `$_SERVER` variables
(a CGI-era behavior), while `getallheaders()` still exposes it. Since
every verification failure fails closed to the decoy, the endpoint
looked healthy from outside while every valid probe was rejected.

Stock WordPress masks this: since WP 5.6 the core `.htaccess` rewrite
block includes `RewriteRule .* -
[E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]`, which re-exports the
header. Hosts that generate their own server config without that rule
(WP Engine among them) hit the bug. That's also why the original wp-env
verification couldn't catch it — wp-env's stock `.htaccess` carries the
compensating rule.

This is the same defect fixed in the SDK by
getsupertab/connect-sdk-php#24, found live on the vanilla-PHP
self-report demo; the plugin duplicated the pre-fix header reading
instead of going through the SDK.

## Key Changes

- **SDK bump `1.4.0-beta.9` → `1.4.0-beta.10`**, which ships
`RequestContext::resolveAuthorizationHeader()` (and fixes the same bug
inside the SDK's own `handleRequest()` path used by `Bot_Protection` for
license-token checks).
- **`Status_Handler::get_authorization_header()`** now delegates to the
SDK resolver: `$_SERVER` wins when populated, otherwise the raw request
headers are searched case-insensitively. New protected
`get_raw_request_headers()` seam wraps `getallheaders()` (guarded by
`function_exists`; unavailable under the CLI SAPI) so tests can inject
headers.
- **3 regression tests**: fallback to raw headers when `$_SERVER` lacks
the header, `$_SERVER` precedence, and empty result when absent
everywhere.

Verified end-to-end in wp-env (Apache + mod_php) via A/B: with the core
`.htaccess` auth rule removed to mirror affected hosts, the old code
never delivered the bearer to the verifier (no JWKS activity, decoy
served); with this fix the same probe reaches `StatusChallengeVerifier`,
fetches the platform JWKS, and fails only on the (deliberately)
unverifiable test token — decoy behavior for invalid/missing challenges
is unchanged.
github-actions Bot pushed a commit to getsupertab/connect-wp that referenced this pull request Jul 14, 2026
# [1.3.0-beta.9](v1.3.0-beta.8...v1.3.0-beta.9) (2026-07-14)

### Bug Fixes

* resolve Authorization via the SDK's getallheaders() fallback in Status_Handler ([#19](#19)) ([d515452](d515452)), closes [getsupertab/connect-sdk-php#24](getsupertab/connect-sdk-php#24)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants