Precursor to #119 Phase 2.
Phase 2 (subscription scoping / data-leakage fix) cannot ship until the
authorization server is running, issues opaque tokens per ESPI 4.0, and exposes
a subscription_id-bearing introspection response. Until then every Phase 2a
design choice is unverifiable against a hypothetical token shape.
See the design analysis posted as a comment on #119:
#119 (comment)
Why this is blocking
ESPI 4.0 requires opaque (reference) access tokens — JWTs are not permitted by
the spec. A resource server cannot read claims from an opaque token; it must
call RFC 7662 token introspection to learn what the token grants. Phase 2's
entire OpaqueTokenIntrospector / ApiAccessValidator design is built around
that introspection response. Without a running auth-server returning the
expected response shape (including subscription_id), no Phase 2 code can be
end-to-end tested.
Shipping a security fix that can't be proven to work against a real token is
the worst outcome for an IDOR / data-leakage fix.
Acceptance criteria
Phase 2.0 is complete when one integration test boots both modules, mints a
real token via the authorization code flow, calls a data-custodian endpoint,
and asserts the principal carries subscription_id. Until that test passes,
no Phase 2a code will be merged.
Checklist
Step 1 — Configuration audit (read-only)
Step 2 — Local dev environment
Step 3 — Verify token format
Step 4 — Add subscription_id to introspection response (the wildcard)
Step 5 — Wire data-custodian as resource server
Step 6 — End-to-end smoke test
Non-goals (deferred)
- Scope-grammar alignment — Phase 7. Phase 2 keeps the existing
SCOPE_FB_* shorthand and pins parsing behind a single helper for later
swap.
- JSON output — Phase 3. Cosmetic
produces = JSON declarations stay as-is.
- Phase 2a/2b/2c code — does not start until step 6 passes.
Estimate
1–2 working days for a senior Spring developer who knows Spring Authorization
Server. Step 4 is the wildcard (Spring Authorization Server's
OAuth2TokenIntrospectionAuthenticationProvider extensibility is the unknown).
🤖 Generated with Claude Code
Precursor to #119 Phase 2.
Phase 2 (subscription scoping / data-leakage fix) cannot ship until the
authorization server is running, issues opaque tokens per ESPI 4.0, and exposes
a
subscription_id-bearing introspection response. Until then every Phase 2adesign choice is unverifiable against a hypothetical token shape.
See the design analysis posted as a comment on #119:
#119 (comment)
Why this is blocking
ESPI 4.0 requires opaque (reference) access tokens — JWTs are not permitted by
the spec. A resource server cannot read claims from an opaque token; it must
call RFC 7662 token introspection to learn what the token grants. Phase 2's
entire
OpaqueTokenIntrospector/ApiAccessValidatordesign is built aroundthat introspection response. Without a running auth-server returning the
expected response shape (including
subscription_id), no Phase 2 code can beend-to-end tested.
Shipping a security fix that can't be proven to work against a real token is
the worst outcome for an IDOR / data-leakage fix.
Acceptance criteria
Phase 2.0 is complete when one integration test boots both modules, mints a
real token via the authorization code flow, calls a data-custodian endpoint,
and asserts the principal carries
subscription_id. Until that test passes,no Phase 2a code will be merged.
Checklist
Step 1 — Configuration audit (read-only)
RegisteredClient/OAuth2AuthorizationServerConfiguration; confirmtokenSettings()format (REFERENCE vs SELF_CONTAINED)/oauth2/introspectis enabled and properly authenticated (client credentials, not bearer)OpaqueTokenIntrospector/OAuth2TokenCustomizerbeansOAuth2Authorizationpersistence; locate where subscription binding could be stored at grant timeoauth2_authorizationtable exists; check for custom columns or join tables linking authorizations to subscriptionsStep 2 — Local dev environment
dev-mysqlprofile)openespi-authserverwithdev-mysql; resolve any Flyway migration drift/oauth2/.well-known/oauth-authorization-serverreturns a valid metadata documentRegisteredClient(third-party app) and one retail customer via seed dataStep 3 — Verify token format
curl/ Postman)eyJ...)RegisteredClienttoOAuth2TokenFormat.REFERENCEand re-testStep 4 — Add
subscription_idto introspection response (the wildcard)OAuth2TokenIntrospectionAuthenticationProvider/ converter to addsubscription_idto the introspection claimsOpaqueTokenIntrospectorthat enriches principal via DB lookupStep 5 — Wire data-custodian as resource server
spring.security.oauth2.resourceserver.opaquetoken.*config pointing at auth-server introspection URLresource-serveras aRegisteredClientwithclient_credentialsgrant + introspection authorityStep 6 — End-to-end smoke test
subscription_idNon-goals (deferred)
SCOPE_FB_*shorthand and pins parsing behind a single helper for laterswap.
produces = JSONdeclarations stay as-is.Estimate
1–2 working days for a senior Spring developer who knows Spring Authorization
Server. Step 4 is the wildcard (Spring Authorization Server's
OAuth2TokenIntrospectionAuthenticationProviderextensibility is the unknown).🤖 Generated with Claude Code