Conversation
Adds asymmetric JWT verification for tokens issued by trusted external IDPs / OID4VP login flows, alongside the existing local HS256 paths which are left unchanged. - config/externalIdp.ts: typed, validated env config (issuers, audience, algs, subject claim, discovery TTL); disabled by default; fails fast if issuers are set without an audience. - libs/jwt/externalVerifier.ts: OIDC discovery through the existing cached axios, per-issuer remote JWKS (jose) with key rotation, signature + iss + aud + exp + alg checks. - libs/jwt/externalIdentity.ts: iss-based routing (peek only, never trusted) and mapping of the external subject (DID) to a local User/UserIdentifier/Participant; rejects unknown subjects with 401 (no JIT provisioning). - middleware/auth.ts + middleware/oauth.ts: additive external branch in verifyUserJWT, verifyParticipantJWT and validateAccessToken. - errors/UnauthorizedError.ts: 401 typed error. - Parameterized tests with a mock discovery + JWKS endpoint, and middleware routing tests. - Docs + .env.sample entries. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reworks the external IDP branch so it adds token verification only, and leaves the user identity model completely untouched. Preserves the configurable discovery path and JWKS proxy support from the previous commits. Identity mapping: - The verified subject is matched against User.email and Participant.did. User.email is the key the codebase already uses for natural persons and is only written when a User is created, so a participant cannot repoint an existing account at a subject it controls. The field is a plain string, so it can equally hold a DID with no schema change. - Dropped the UserIdentifier.identifier lookup entirely. Those documents are participant-scoped and participant-writable, so matching them let the party supplying the value also choose whose account it resolved to. - An ambiguous match (more than one record) is rejected rather than resolved arbitrarily. - When the subject claim is `email`, `email_verified: true` is required; an unverified address is self-asserted and cannot bind an account. Verification hardening: - exp/iat and the subject claim are now required to be present, not just valid when present, so an issuer omitting exp cannot mint a token that never expires. - EXTERNAL_OIDC_ALGS is validated against an asymmetric allowlist; issuer URLs and the advertised jwks_uri must be https; the discovery document must describe the issuer it was fetched for. - Discovery and JWKS requests have a timeout, a short failure backoff, and a configurable clock tolerance (EXTERNAL_OIDC_HTTP_TIMEOUT, EXTERNAL_OIDC_CLOCK_TOLERANCE). The configured TTL now also drives the JWKS cache, which previously used jose's defaults. - Issuer unreachable is a new IdpUnavailableError mapped to 503, rather than reporting a valid credential as 401. - 401 responses carry a uniform message so they are not an oracle for which check failed or whether a subject is enrolled. - Trailing slashes are normalised on both configured and presented issuers, so the two spellings of one issuer no longer diverge between routing and verification. Middleware: - resolveTokenRoute moved inside the try blocks; on Express 4 a throw from an async middleware became an unhandled rejection, so a config error hung the request instead of failing at startup. The config is now also parsed eagerly in startServer. - verifyParticipantJWT no longer caches the external identity in the session, which had let a session outlive the token. - Shared authenticateExternalToken/sendExternalAuthError helper replaces the branch duplicated across the three middlewares. - UnauthorizedError and IdpUnavailableError are handled by globalErrorHandler. Packaging: - http-proxy-agent dropped: jwks_uri is required to be https, so only the https agent can ever apply. - https-proxy-agent added to package-lock.json, where package.json declared it but the lockfile had no entry at all - npm ci would refuse to install. Tests: - Added the missing core case: a trusted `iss` signed by a key outside the issuer's JWKS. Also no-exp tokens, discovery issuer mismatch, cleartext jwks_uri, discovery errors, and configuration validation. - resetExternalVerifierCaches now empties the discovery cache storage, which had leaked a cached document between cases and made the "no jwks_uri" case pass for the wrong reason. - New externalIdentity.spec.ts covers the mapping, including ambiguity and the email_verified gate. - Middleware tests assert that a failed external verification does not fall back to the local HMAC path. Docs: - EXTERNAL_OIDC_DISCOVERY_PATH and the HTTPS_PROXY/NO_PROXY behaviour are documented in README and .env.sample. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two conflicts, both additive on each side: - .env.sample: upstream renamed X_VISIONSTRUST_CONSENT_KEY to X_CATALOG_CONSENT_KEY. Took the rename and kept the appended EXTERNAL_OIDC_* block. - src/middleware/globalErrorHandler.ts: upstream added a NotFoundError branch, an eslint-disable for the unused `next`, and removed the unreachable trailing next(err). This branch added UnauthorizedError (401) and IdpUnavailableError (503). Kept upstream's structure with all three imports and all four error branches; the types are disjoint so the chain order carries no meaning.
wistefan
marked this pull request as ready for review
September 1, 2026 13:30
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #34
Adds asymmetric JWT verification for tokens issued by trusted external IDPs / OID4VP login flows, alongside the existing local HS256 paths which are left unchanged.