Auth Header Migration Findings

Date: 2026-07-16

This note captures a static code-inspection pass over api.user.m7.org to understand whether the current API primarily authenticates through modern HTTP headers or through request/body fields such as auth.token.

Summary

The repo currently looks mid-migration.

  • Public docs and CORS settings already point callers toward Authorization: Bearer ....
  • The actual request parser and shared auth path still primarily read token and auth context from request/body fields such as auth.token, token, auth.dpop, and related values.
  • Based on the code inspected here, header-only bearer auth does not appear to be the real primary contract today unless something outside this repo rewrites headers into request fields before PHP parses the request.

This was a static inspection only. No runtime verification was performed.

Key Findings

1. The live request parser is request-field driven

The endpoint stack calls lib\HTTP\Request::parse(...), which:

  • starts from $_REQUEST
  • merges JSON request bodies
  • maps underscore field names like auth_token into dotted keys like auth.token

It does not inspect Authorization, DPoP, or other auth headers.

Relevant code:

  • ../config/general.json
  • /Users/hr/personal/code/php/m7-php-platform/route/endpoint/v1/Endpoint.php
  • /Users/hr/personal/code/php/m7-php-lib/HTTP/Request.php

2. Shared auth expects auth.token or token

The base API controller and the shared auth service both pull the token from the parsed request object rather than directly from headers.

Relevant code:

  • ../lib/php/thisProject/api/Controller.php
  • /Users/hr/personal/code/php/m7-php-platform/package/auth/user/registry/service/Auth.php

This affects both user and member validation flows.

3. Public docs are ahead of implementation

Several public-facing docs say callers should send:

  • Authorization: Bearer <ACCESS_TOKEN>

Example:

  • ../markdown_docs/session-me.md

But the actual /session/me handler still reads:

  • auth.token
  • token

Relevant code:

  • ../lib/php/thisProject/api/session/Me.php

4. CORS is header-ready, but auth ingestion is not

The public route entrypoint allows these request headers:

  • Authorization
  • DPoP
  • token

Relevant code:

  • ../public_html/route.php

That means the surface is at least prepared to receive modern auth headers, but the parsed request object used by the app does not currently normalize those headers into the auth fields the handlers expect.

5. This is not just a token-transport migration

The auth.* shape is used for more than bearer access tokens.

Examples:

  • login and root-session flows use auth.user, auth.pass, auth.code, and auth.org
  • exchange flows use auth.token or token, plus a mix of auth.dpop, auth.htu, auth.fingerprint, and body-side dpop, htu, fingerprint
  • some authenticated routes still use body-side auth.pass for current password checks

Relevant code:

  • ../lib/php/thisProject/api/auth/user/RootSession.php
  • ../lib/php/thisProject/api/auth/member/RootSession.php
  • ../lib/php/thisProject/api/auth/user/Exchange.php
  • ../lib/php/thisProject/api/auth/member/Exchange.php
  • ../lib/php/thisProject/api/me/Password.php

Practical Read

Today, the safest working assumption is:

  • api.user.m7.org still primarily authenticates through parsed request/body fields
  • bearer-header auth is documented and partially surfaced, but not yet the dominant implementation path in this repo

Likely Migration Seam

If this cleanup is resumed later, the least-risk seam appears to be:

  1. normalize Authorization, DPoP, and any other modern auth headers into the same internal request shape the app already expects
  2. keep the existing auth.* readers working during transition
  3. then decide which legacy request/body auth fields should remain supported, and for which endpoint families

That would let the request boundary modernize first without forcing every downstream handler to change at once.

Mounted Input Classes

At a high level, the live mounted API surface does break down into two broad request classes:

  • routes that use the base authenticated path (credentials enabled)
  • routes that opt out of base credentials (credentials => false) and do their own relay, token, or request validation

That distinction is real, and it gives a useful migration heuristic:

  • guarded endpoints
    • prefer normalizing modern header inputs into the guarded request shape and let the existing auth path keep doing the work
  • unguarded endpoints
    • split these into relay/deadDrop-guarded versus truly naked/manual-token routes

In other words:

  • guarded endpoints look easier to migrate centrally
  • unguarded endpoints should not be treated as one bucket

Guarded endpoints

The credentials-enabled side is the normal authenticated surface.

For header migration purposes, this is the direct conversion class:

  • convert callers to send modern HTTP auth headers instead of request/body auth fields where appropriate
  • read Authorization, DPoP, and related modern auth inputs at the request boundary
  • normalize them into the same internal request/auth shape the guarded path already expects
  • let the existing controller/service validation path continue from there

Operationally, that likely means:

  • migrate guarded endpoints first
  • update frontend callers to send header-based auth inputs
  • preserve internal compatibility by mapping those headers into the existing request/auth structure during the transition

Unguarded endpoints

The credentials => false side needs a second split:

  • relay / deadDrop guarded
  • truly naked or otherwise special-cased

Unguarded but relay / deadDrop guarded

These mounted routes all opt out of base credentials and then immediately require a consumed SSO relay or deadDrop payload before doing their work:

  • POST /auth/user/login
  • POST /auth/user/root-session
  • POST /auth/user/exchange
  • POST /auth/member/login
  • POST /auth/member/root-session
  • POST /auth/member/exchange
  • POST /auth/signup
  • POST /oauth/client_credentials/issue

Relevant code:

  • ../lib/php/thisProject/targets/api/v2/AuthAPI.php
  • ../lib/php/thisProject/targets/api/v2/MemberAuthAPI.php
  • ../lib/php/thisProject/targets/api/v2/SessionAPI.php
  • ../lib/php/thisProject/targets/api/v2/OauthAPI.php
  • ../lib/php/thisProject/api/auth/user/Login.php
  • ../lib/php/thisProject/api/auth/user/RootSession.php
  • ../lib/php/thisProject/api/auth/user/Exchange.php
  • ../lib/php/thisProject/api/auth/member/Login.php
  • ../lib/php/thisProject/api/auth/member/RootSession.php
  • ../lib/php/thisProject/api/auth/member/Exchange.php
  • ../lib/php/thisProject/api/auth/Signup.php
  • ../lib/php/thisProject/api/oauth/client_credentials/Issue.php
  • ../lib/php/thisProject/helper/SsoBridgeGate.php
  • ../lib/php/thisProject/api/auth/Controller.php
  • ../lib/php/thisProject/api/auth/member/Controller.php
  • ../lib/php/thisProject/api/oauth/client_credentials/Controller.php

For migration planning, these look lower-risk:

  • they are unguarded at the base-controller level
  • but they are not truly naked
  • they can likely be left alone initially if the relay/deadDrop contract itself is not changing

Unguarded and truly naked or otherwise special-cased

There are mounted exceptions that should be addressed individually:

  • POST /session/me
    • opts out of base credentials, but manually reads auth.token or token, identifies the token locally, and then calls user/member validation
  • POST /oauth/clients/connections/verify
    • opts out of base credentials, requires a token request field, and calls upstream verify using the service master key

So the best current read is:

  • the live unguarded mint / exchange flows are almost entirely SSO relay or deadDrop guarded
  • those relay/deadDrop-guarded unguarded routes can probably be left alone in a first-pass header migration
  • the truly naked or special-cased unguarded routes should be handled individually

Current examples of the individually handled bucket:

  • POST /session/me
    • likely deprecated, but still a direct manual-token path today
  • POST /oauth/clients/connections/verify
    • not a tenant-auth/member-auth path; it behaves more like an app-to-app token/connection allowlist check

Inventory Of auth.* Request Reads

This section is meant as a change map.

It lists code-path reads of:

  • auth.* request fields
  • whole auth request objects
  • shared helpers on the active api.user request path that still depend on those values

It intentionally does not list:

  • comments or docstrings alone
  • outbound payload writers that send auth to downstream services
  • unrelated field names such as oauth.*

Request parser aliases

The parser currently maps underscore request keys into dotted auth keys:

  • auth_user -> auth.user
  • auth_code -> auth.code
  • auth_id -> auth.id
  • auth_method -> auth.method
  • auth_token -> auth.token
  • auth_pass -> auth.pass
  • auth_org -> auth.org

Relevant code:

  • ../config/general.json

Shared readers on the main API path

  • ../lib/php/thisProject/api/Controller.php Reads auth.token or token in the base controller constructor before route auth validation.
  • /Users/hr/personal/code/php/m7-php-platform/package/auth/user/registry/service/Auth.php Reads auth.token or token in both user and member validation paths, and also reads auth.dpop, auth.htu, and auth.fingerprint.

These are the two most important shared reads because many routes inherit them without touching auth.* directly in the route file.

Relay and OAuth-forwarding helpers

  • ../lib/php/thisProject/helper/SsoBridgeGate.php Reads auth.client_id and auth.org when matching route input against relay context for root-session flows.
  • ../lib/php/thisProject/api/auth/Controller.php Reads auth.client_id in oauthForwardedContext(...) and requestHasOauthForwardedOverrides(...).
  • ../lib/php/thisProject/api/auth/member/Controller.php Reads auth.client_id in oauthForwardedContext(...) and requestHasOauthForwardedOverrides(...).

These matter because some endpoint files only read a few auth.* fields directly, but still inherit additional auth.client_id behavior through these helpers.

Public and management endpoints

  • ../lib/php/thisProject/api/session/Me.php Reads auth.token or token.
  • ../lib/php/thisProject/api/me/Password.php Reads auth.pass as one of the accepted sources for the current password.
  • ../lib/php/thisProject/api/client/ChangePass.php Reads auth.fingerprint, auth.dpop, and auth.htu.
  • ../lib/php/thisProject/api/oauth/client_credentials/IssueWeb.php Reads auth.fingerprint as a fallback source.
  • ../lib/php/thisProject/api/admin/user/Controller.php Reads auth.token or token, plus auth.fingerprint, auth.dpop, and auth.htu in remote-auth setup.
  • ../lib/php/thisProject/api/org/identity/Controller.php Reads auth.token or token, plus auth.fingerprint, auth.dpop, and auth.htu in remote-auth setup.

Consumer auth flows

  • ../lib/php/thisProject/api/auth/Login_traditional.php Reads auth.user, auth.id, auth.pass, and auth.fingerprint.
  • ../lib/php/thisProject/api/auth/user/RootSession.php Reads auth.user, auth.id, auth.pass, and auth.code. Also inherits auth.client_id checking through SsoBridgeGate.
  • ../lib/php/thisProject/api/auth/user/Login.php Reads auth.user, auth.id, auth.pass, auth.code, and auth.fingerprint. Also inherits auth.client_id handling through oauthForwardedContext(...).
  • ../lib/php/thisProject/api/auth/user/Exchange.php Reads auth.dpop, auth.method, auth.token, auth.fingerprint, and auth.htu. Also inherits auth.client_id handling through oauthForwardedContext(...).

Member auth flows

  • ../lib/php/thisProject/api/auth/member/RootSession.php Reads auth.user, auth.id, auth.pass, auth.org, and auth.code. Also inherits auth.client_id and relay/org matching through SsoBridgeGate.
  • ../lib/php/thisProject/api/auth/member/Login.php Reads auth.user, auth.id, auth.pass, auth.org, auth.code, and auth.fingerprint. Also inherits auth.client_id handling through oauthForwardedContext(...).
  • ../lib/php/thisProject/api/auth/member/Exchange.php Reads auth.dpop, auth.method, auth.token, auth.fingerprint, auth.htu, and auth.org. Also inherits auth.client_id handling through oauthForwardedContext(...).

Retained legacy handlers

  • ../lib/php/thisProject/api/auth/user/legacy/DelegatedLogin.php Reads the whole auth object plus auth.id, auth.user, auth.pass, and auth.code.
  • ../lib/php/thisProject/api/auth/member/legacy/DelegatedLogin.php Reads the whole auth object plus auth.user and auth.pass.
  • ../lib/php/thisProject/api/auth/member/legacy/Exchange.php Reads auth.token and auth.fingerprint.

Important implementation mismatches

Some routes still document or imply auth.* inputs, but the current code reads bare fields instead for part of the request:

  • ../lib/php/thisProject/api/auth/user/RootSession.php Reads bare dpop, htu, and fingerprint, not auth.dpop, auth.htu, or auth.fingerprint.
  • ../lib/php/thisProject/api/auth/member/RootSession.php Reads bare dpop, htu, and fingerprint, not auth.dpop, auth.htu, or auth.fingerprint.

That mismatch matters because a future migration cannot assume that every binding-related input still lives under auth.* even within the same auth family.

Open Question

The main unresolved question from this pass is whether some upstream layer outside this repo rewrites headers into request fields before PHP sees them. If that exists, the runtime behavior may be less broken than the local code alone suggests.

1