Skip to content

fix(identity-webhook): OIDC composite app_<24hex>_<secret> token exchange#63

Merged
eliteprox merged 8 commits into
mainfrom
feat/composite-app-token-exchange
Jul 17, 2026
Merged

fix(identity-webhook): OIDC composite app_<24hex>_<secret> token exchange#63
eliteprox merged 8 commits into
mainfrom
feat/composite-app-token-exchange

Conversation

@eliteprox

@eliteprox eliteprox commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Accept composite Bearer app_<24hex>_<secret> in OIDC mode when OIDC_TOKEN_EXCHANGE_BASE_URL is set.
  • RFC 8693 token exchange at /api/v1/apps/{clientId}/oidc/token, then jwtVerify the minted access token (JWKS via discovery from fix(identity-webhook): resolve JWKS via OIDC discovery #62).
  • Secret segment is opaque (operators may use their own key prefix); reject cs_ / _cs_ client-secret shapes.
  • Harden exchange: salted HKDF cache keys, allowlist-sanitized logs; document new env vars in README / .env.example.

Behavior

  • Opt-in: without OIDC_TOKEN_EXCHANGE_BASE_URL, OIDC mode stays JWT-only.
  • Composite shape is app_ + 24 lowercase hex + _ + opaque secret (underscore separator for copy/select UX).
  • Optional M2M client credentials: OIDC_EXCHANGE_M2M_CLIENT_ID + OIDC_EXCHANGE_M2M_CLIENT_SECRET (both or neither).
  • Wired through createEndUserVerifierFromEnv (Docker / Compose path).

Test plan

Notes

  • npm publish / legacy-env packaging is not in this PR (deferred).

Replace the incorrect default {issuer}/.well-known/jwks.json with
issuer-relative OIDC discovery (openid-configuration → jwks_uri).
Validate discovery issuer, wrap JWKS fetch errors with the URL, and
keep OIDC_JWKS_URI as an explicit override. Unit tests cover discovery
and leave api_key mode unchanged.
@eliteprox
eliteprox force-pushed the fix/oidc-jwks-discovery branch from 70f732d to 301770e Compare July 11, 2026 00:30
@eliteprox
eliteprox force-pushed the feat/composite-app-token-exchange branch from 56c5708 to 97c538a Compare July 11, 2026 00:32
@eliteprox

Copy link
Copy Markdown
Collaborator Author

Rebased onto retargeted fix/oidc-jwks-discovery (#62). Dropped npm-publish / legacy-env packaging from this PR; only the composite exchange feature remains. CI green.

@eliteprox eliteprox changed the title feat(identity-webhook): composite app token exchange in OIDC mode feat(identity-webhook): OIDC composite app_*.pmth_* token exchange Jul 11, 2026
Accept Bearer app_<24hex>_<secret> when OIDC_TOKEN_EXCHANGE_BASE_URL is set:
RFC 8693 exchange at the app-scoped token endpoint, then jwtVerify the minted
access token (same JWKS discovery path as #62). Secret segment is opaque
(operator prefixes allowed); reject cs_ / _cs_ client-secret shapes. Harden
cache keys (salted HKDF) and log sanitization. Document exchange env vars.
@eliteprox
eliteprox force-pushed the feat/composite-app-token-exchange branch from 97c538a to e3a868b Compare July 11, 2026 21:43
@eliteprox eliteprox changed the title feat(identity-webhook): OIDC composite app_*.pmth_* token exchange feat(identity-webhook): OIDC composite app_<24hex>_<secret> token exchange Jul 11, 2026
Base automatically changed from fix/oidc-jwks-discovery to main July 16, 2026 15:12
Copilot AI review requested due to automatic review settings July 16, 2026 15:26

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds optional support in identity-webhook OIDC mode for composite Bearer app_<24hex>_<secret> credentials by exchanging them for an access token via RFC 8693 token exchange, then verifying the minted JWT, with documentation and tests to match.

Changes:

  • Implement composite token parsing + RFC 8693 exchange flow in createOidcVerifier, including caching and hardened logging.
  • Add unit tests for composite parsing/URL normalization and the end-to-end exchange→JWT verify path.
  • Document new env vars in README.md and .env.example.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
README.md Documents new OIDC token-exchange env vars and composite bearer behavior.
identity-webhook/verifiers.mjs Implements composite key parsing, token exchange, caching, and JWT verification integration.
identity-webhook/verifiers.test.mjs Adds coverage for composite parsing/normalization and exchange-based verification flow.
.env.example Adds example env vars for enabling token exchange and optional M2M credentials.

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

Comment thread identity-webhook/verifiers.mjs Outdated
Comment thread identity-webhook/verifiers.mjs Outdated
Comment thread identity-webhook/verifiers.mjs
Expire stale composite-exchange inflight cache entries, require Bearer
scheme, reject non-origin token-exchange URLs, expand JWT algs to
RS256/ES256, require exp with 60s clock skew, and normalize OIDC issuer
consistently for discovery and jwtVerify.
Copilot AI review requested due to automatic review settings July 16, 2026 18:39
eliteprox added a commit to pymthouse/clearinghouse that referenced this pull request Jul 16, 2026
Expire stale composite-exchange inflight cache entries, require Bearer
scheme, reject non-origin token-exchange URLs, expand JWT algs to
RS256/ES256, require exp with 60s clock skew, and normalize OIDC issuer
consistently for discovery and jwtVerify.
@eliteprox

Copy link
Copy Markdown
Collaborator Author

Addressed Copilot review + related OIDC hardening in 378b79b:

  • Cache: inflight composite-exchange entries now expire via expiresAt (same as results); unit test added.
  • Algs: ALLOWED_JWT_ALGS is RS256/ES256 (documented in README).
  • Exchange base URL: must be origin-only; path/query/hash are rejected instead of silently dropped.
  • Also: require Bearer scheme; normalize issuer for discovery + jwtVerify; requiredClaims: [exp, subjectClaim]; clockTolerance: 60.

Same commit cherry-picked to pymthouse#6 (be32152).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment thread identity-webhook/verifiers.mjs
Comment thread identity-webhook/verifiers.mjs
…e races

Allow jwtVerify to match iss with or without a trailing slash after
issuer normalization, and only clear/set composite-exchange cache
entries when they still reference the same inflight promise.
Copilot AI review requested due to automatic review settings July 16, 2026 20:05
@eliteprox

Copy link
Copy Markdown
Collaborator Author

Addressed the two latest Copilot comments in $(git rev-parse --short HEAD):

  1. Trailing-slash iss: jwtVerify now accepts both normalized issuer and ${issuer}/ so tokens whose iss ends with / still verify after discovery normalization.
  2. Inflight cache race: clearInflight / setResultForInflight only mutate the entry when it still points at the same promise, so an expired older exchange cannot clobber a newer one.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment thread identity-webhook/protocol.mjs
Comment thread identity-webhook/verifiers.mjs
…extraction

Refactor the bearerToken function to enhance token extraction from the authorization header. The new implementation checks for the "Bearer" scheme and handles leading spaces more robustly. Additionally, add tests to cover various cases, including malformed input and trailing spaces, ensuring the function behaves correctly under different scenarios.
Copilot AI review requested due to automatic review settings July 16, 2026 20:09
…xtraction

Update the bearerToken function to utilize a regular expression for more concise and efficient token extraction from the authorization header. This change improves readability and maintains functionality by ensuring proper handling of the "Bearer" scheme and leading spaces.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comment thread identity-webhook/verifiers.mjs
Comment thread README.md Outdated
Comment thread .env.example Outdated
Copilot AI review requested due to automatic review settings July 16, 2026 20:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Address remaining PR #63 Copilot review: reject username/password in
OIDC_TOKEN_EXCHANGE_BASE_URL (origin drops them silently), document
HTTPS-except-loopback in README/.env.example, and cover RFC 6750
whitespace rejection in bearerToken tests.
Copilot AI review requested due to automatic review settings July 16, 2026 22:46
@eliteprox

Copy link
Copy Markdown
Collaborator Author

Addressed remaining Copilot review in e0be4d7:

  • Userinfo: reject username/password in OIDC_TOKEN_EXCHANGE_BASE_URL (would be silently dropped by url.origin)
  • Docs: README + .env.example note HTTPS-except-loopback and no userinfo
  • bearerToken: already RFC 6750 b64token; added whitespace rejection tests

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@eliteprox

Copy link
Copy Markdown
Collaborator Author

Manual smoke — composite exchange against local clearinghouse stack

Tested on a stacked branch (feat/composite-app-token-exchange + feat/balance-gate + feat/owner-m2m-customer-identity) with Compose (kafka, identity-webhook, remote-signer, openmeter-collector).

Config

  • IDENTITY_AUTH_MODE=oidc
  • OIDC_TOKEN_EXCHANGE_BASE_URL=https://staging.pymthouse.com
  • OIDC_ISSUER / OIDC_AUDIENCE = https://staging.pymthouse.com/api/v1/oidc
  • OIDC_CLIENT_CLAIM=client_id, OIDC_SUBJECT_CLAIM=external_user_id, OIDC_REQUIRED_SCOPES=sign:job

Composite Bearer used

Authorization: Bearer app_a559adff001a1cbc50f53dac_pmth_<secret>

(app_<24hex>_<opaque> — exchanged via RFC 8693 at /api/v1/apps/{clientId}/oidc/token, then JWT verify)

Authorize (identity-webhook)

{
  "status": 200,
  "auth_id": "app_a559adff001a1cbc50f53dac:895773a5-7470-4638-bdf9-a2b3d15906b8",
  "identity": {
    "issuer": "http://identity-webhook:8090",
    "client_id": "app_a559adff001a1cbc50f53dac",
    "usage_subject": "895773a5-7470-4638-bdf9-a2b3d15906b8",
    "usage_subject_type": "external_user_id"
  }
}

Live LV2V stream (local signer 127.0.0.1:8081)

  • Gateway: livepeer-python-gateway write_frames.py
  • Orchestrator: ai.livepeer-utopia.xyz:8935
  • Model: streamdiffusion-sdxl (60 frames @ 10 fps)
  • Result: job started + frames published; remote-signer signed tickets with the composite-resolved auth_id above (e.g. Signed numTickets=… auth_id=app_a559…:895773a5-…)

Metering

Kafka create_signed_ticket events from the live payments were consumed by openmeter-collector and POSTed to OpenMeter (output_error=0).

Covers the optional smoke item in the PR test plan: composite key against a staging exchange endpoint, end-to-end through the Compose identity webhook + remote signer.

@eliteprox
eliteprox requested a review from rickstaa July 17, 2026 20:19
@eliteprox

Copy link
Copy Markdown
Collaborator Author

peer reviewed by @rickstaa, merging per discussion (change is bug fix, rather than feature)

@eliteprox eliteprox changed the title feat(identity-webhook): OIDC composite app_<24hex>_<secret> token exchange fix(identity-webhook): OIDC composite app_<24hex>_<secret> token exchange Jul 17, 2026
@eliteprox
eliteprox merged commit 62dea07 into main Jul 17, 2026
2 of 3 checks passed
@eliteprox
eliteprox deleted the feat/composite-app-token-exchange branch July 17, 2026 20:25
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