Skip to content

AS↔DC token/introspection wire format diverges from ESPI standard (Batch URI forms + non-standard *_id claims) #160

Closed

Description

@dfcoffin

Defect (live bug, surfaced while scoping the #150 contract)

The C4 token-response / introspection augmentation (merged in #147) does not match the ESPI 4.0 standard token-response format (REQ.21, verified against the standard's own example: "resourceURI":".../resource/Batch/Subscription/1dfa07c5740a_118328", "scope":"FB=...;...", no *_id fields, "customerResourceURI":"" allowed).

Producer divergence — openespi-common SubscriptionProvisioningServiceImpl

subscriptionUri(id)  = resourceBaseUri + "/Subscription/"  + id   // line ~195  -> MISSING "/Batch/"
authorizationUri(id) = resourceBaseUri + "/Authorization/" + id   // line ~199  -> OK
Field ESPI standard Current Status
resourceURI .../resource/Batch/Subscription/{id} .../resource/Subscription/{id} ❌ missing /Batch/
authorizationURI .../resource/Authorization/{id} same ✓
customerResourceURI .../resource/Batch/RetailCustomer/{rc} or "" pass-through from AS handoff (unverified) ⚠️
authorization_id, resource_subscription_id, customer_subscription_id not in the standard AS GrantBackchannelTokenCustomizer adds all three ❌ drop
scope FB=... grammar only granted scope verify no OIDC leak

Smoking gun: producer already contradicts DC's own consumer

DC's ResourceValidationFilter reads resourceURI off the token and expects the /resource/Batch/Subscription/{id} form (uri.contains("/resource/Batch/Subscription")). The producer emits /resource/Subscription/{id} — so a real token's resourceURI already fails DC's own resource validation. The two halves were written against different assumptions; the consumer was right, the producer wrong.

Root-cause fix (not a string patch)

The drift exists because producer and consumers each encode the ESPI URL format independently. Introduce one canonical ESPI Batch-URI builder + parser in openespi-common, and route the producer (SubscriptionProvisioningServiceImpl) and consumers (ResourceValidationFilter, the DC introspector URI parsing) through it — same code builds and parses, so drift becomes structurally impossible.

Then:

  • Correct customerResourceURI to Batch/RetailCustomer/{rc} (and "" when no PII).
  • Drop the three non-standard *_id claims (AS customizer + DTOs); the URIs are the contract, DC parses ids from them. (Verify openespi-thirdparty doesn't read the *_id fields.)
  • Verify scope is FB-grammar only and that the URI claims actually reach /oauth2/introspect.

Verification / lock

ESPI-standard contract fixtures (Subscription + Bulk) + producer/consumer contract tests (#150). Test-first red→green.

Acceptance

  • Producer emits standard Batch/{Subscription|Bulk|RetailCustomer} URIs via the canonical builder
  • ResourceValidationFilter + introspector parse via the same canonical parser
  • *_id claims removed; scope FB-grammar only verified
  • Contract fixtures + tests pin both sides (CI-enforced where the module runs in CI)

Activity

  1. added
    bugSomething isn't working
    NAESB ESPI StandardRequired to comply with the NAESB ESPI standard
    blockingBlocks other work or CI
    on Jun 4, 2026
  2. added a commit that references this issue on Jun 4, 2026
  3. added 3 commits that reference this issue on Jun 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    NAESB ESPI StandardRequired to comply with the NAESB ESPI standardblockingBlocks other work or CIbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions