Skip to content

[#1221] e2e characterization tests for OIDC discovery, JWKS and idtokeninfo - #1222

Merged
maximthomas merged 4 commits into
OpenIdentityPlatform:masterfrom
maximthomas:features/1075-e2e-oidc-discovery
Nov 6, 2026
Merged

maximthomas merged 4 commits into
OpenIdentityPlatform:masterfrom
maximthomas:features/1075-e2e-oidc-discovery

Conversation

@maximthomas

@maximthomas maximthomas commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Why

#1221 (a step of #1075) pins the HTTP contract of /oauth2 with e2e tests that pass on master, where Restlet still serves /oauth2. The endpoint families then move to CHF one PR at a time. A test that passes unchanged after a move is that PR's parity proof, and any expectation a move changes must be a divergence listed in that PR.

This is the first part of #1221: OIDC discovery, connect/jwk_uri, id_token issuance and idtokeninfo. It adds no production code.

What

  • e2e/oauth2/oidc-test.spec.mjs: 11 tests, 7 ported from the branch and 4 new.
  • e2e/common/oauth2-fixtures.mjs: the fixtures they need.

The 7 ported tests are lines 1-202 of e2e/oauth2/oidc-test.spec.mjs at maximthomas/OpenAM@59a89d07cc, the parent of bccbc9c4d9, which moved /oauth2 to CHF on the abandoned features/restlet-migration branch of the fork. The other 13 tests of that file cover connect/checkSession and connect/endSession and come in the next PR, together with their fixtures (post-logout redirect URIs, clientSessionURI, a client without it).

Fixtures

  • OIDC_CLIENT_SECRET and ensureOidcClient: a confidential client whose id_tokens are signed with HS256. The secret is long because HS256 signs with it and needs a key of at least 256 bits; a short one makes the token endpoint answer a bare server_error.
  • pkce, sessionContext and authorizationCodeTokens: a full authorization_code exchange with an S256 challenge, authenticated by the session cookie. /oauth2/authorize does not accept the iPlanetDirectoryPro header. authorizationCodeTokens returns {code, tokens}, so a test can check c_hash and still compare the token response whole. An authorize redirect without a code (redirect_uri?error=..., the login page) throws with its Location instead of sending no code to the token endpoint.
  • REQUIRED_SCOPES gains openid and profile, so ensureProviderConfig adds them to the shared provider's supported scopes. It only ever adds scopes.

Not taken from the branch: its ensureOAuth2Provider wrote codeVerifierEnforced: true, but only when it created the provider, so the setting depended on which spec file ran first. This PR leaves the setting as configured. On master it defaults to true and no fixture changes it; authorizationCodeTokens sends an S256 challenge either way.

Changed expectations

None. The branch spec, trimmed to the 7 ported tests and run unchanged against master, passed 7/7. Master's changes since the branch was recorded do not touch these answers.

Assertions tightened beyond the branch

All values were recorded from master.

  • Content-Type is asserted exactly. Discovery, connect/jwk_uri and the 200 from idtokeninfo answer application/json;charset=UTF-8. The two 400 answers of idtokeninfo answer application/json without a charset. The branch checked toContain("application/json"), and its header comment said Restlet answers bare application/json; that holds only for the errors.
  • The discovery document's member set is compared whole (22 members), and each of its 11 URL members is compared with the URL built from OPENAM_BASE. The branch checked 7 of them; registration_endpoint, introspection_endpoint, revocation_endpoint and device_authorization_endpoint are new.
  • The realm-prefixed path /oauth2/realms/root/.well-known/openid-configuration must answer the same document as the bare path, compared whole. The branch checked two members. The test is renamed to the realm-prefixed discovery path answers the same document.
  • The JWK set has exactly one top-level member, keys.
  • The token response's member set is compared whole (access_token, expires_in, id_token, nonce, refresh_token, scope, token_type), and scope holds exactly openid and profile.
  • Both idtokeninfo errors compare the whole body: {"error":"bad_request","error_description":"no id_token in request"} and {"error":"bad_request","error_description":"invalid id_token: not right number of dots, 2"}. The branch checked the second description with toContain("invalid id_token").
  • at_hash and c_hash are compared with the unpadded base64url left half of the SHA-256 digest of the access token and of the code (HS256). The branch checked that they were present.
  • A test.skip on a failed amadmin login is now an expect, and the demo login is checked too, so a broken setup fails instead of skipping.
  • The authorize and token exchange runs in the beforeAll of the issuance/idtokeninfo describe, not at file level. A move that breaks /oauth2/authorize or /oauth2/access_token no longer turns the discovery and connect/jwk_uri tests red.

New tests

None of them is on the branch. Each answer was recorded on master, and each test pins the status, the exact Content-Type and the whole body.

  • the discovery path of an unknown realm answers 404: /oauth2/realms/root/realms/no-such-realm/.well-known/openid-configuration answers 404 application/json {"code":404,"reason":"Not Found","message":"No mapping organization found for organization identifier: /no-such-realm"}. The realm-prefixed test above only uses root, so on its own it cannot tell realm routing from a router that drops the realm segment.
  • idtokeninfo client authentication (idTokenInfoClientAuthenticationEnabled defaults to true, and HS256 is an HMAC algorithm): a wrong client secret and a request without client credentials both answer 400 {"error":"invalid_client","error_description":"no registered client matches audience of id_token"}. Master gives a failed client authentication the same description as an aud that matches no client: the InvalidClientException catch in IdTokenInfo covers both. This answer is the contract: a move keeps it, or lists the change as a divergence.
  • idtokeninfo signature check: an id_token whose first signature character is flipped answers 400 {"error":"bad_request","error_description":"invalid id_token"}.

The fixture comment on idTokenSignedResponseAlg said an RS256 client makes idtokeninfo fail with server_error. Run on master, it answers 400 invalid id_token: this client registers no public key. The comment is corrected.

Not pinned:

  • Server: Restlet-Framework/2.4.4, which every one of these answers carries: a framework value.
  • scopes_supported, response_types_supported, subject_types_supported and id_token_signing_alg_values_supported stay arrayContaining, as on the branch. scopes_supported mirrors the shared provider, which the parallel specs widen.

How it was checked locally

The image was FROM openidentityplatform/openam:latest with the WAR replaced, configured the way build-docker configures it (embedded stores locally; CI uses an external OpenDJ user store). The WAR is a local full build of master 2a80c16a9a.

  • The branch spec, trimmed to the 7 ported tests: 7 passed, twice on one container.
  • The first version of this spec (7 tests) on two fresh containers, two runs each: 7 passed every time. A copy with one pinned value changed in each test: 7 failed in both runs, each at its changed assertion.
  • This spec (11 tests), twice on one container: 11 passed in both runs.
  • A production mutant: IdTokenInfo compiled without the client authentication call and without the signature check, patched into a copy of the WAR. 3 failed and 8 passed in both runs. The 3 are the wrong-secret, no-credentials and forged-signature tests, and each got a 200 with the claim set.
  • Shared server: every spec under e2e/oauth2, e2e/uma and e2e/xacml together (oidc 11, oauth2-test 5, device-flow-test 9, webfinger 11, uma 14, xacml 23), twice on one fresh container: 73 passed in both runs. No master spec turned red next to this one.

Part of #1221.

The provider's codeVerifierEnforced defaults to true and the device flow
does not consult it, so the shared provider does enforce PKCE for
authorization requests.
@maximthomas maximthomas added enhancement javascript Pull requests that update Javascript code oauth2 OAuth2 / OpenID Connect tests Test suite: coverage, fixtures, or test infrastructure labels Nov 5, 2026
…gnature check and unknown-realm discovery

Four tests recorded on master: idtokeninfo with a wrong client secret and
without client credentials (400 invalid_client), with a forged signature
(400 invalid id_token), and discovery under an unknown realm (404).

at_hash and c_hash are compared with the left half of the SHA-256 digest
of the access token and the code. The authorize and token exchange runs
only for the issuance and idtokeninfo tests, so discovery and jwk_uri no
longer depend on it. authorizationCodeTokens returns the code next to the
token response and fails with the Location when authorize redirects
without a code. The RS256 note on the OIDC client names the measured
answer.
@maximthomas
maximthomas marked this pull request as ready for review November 6, 2026 05:38
@maximthomas
maximthomas requested a review from vharseko November 6, 2026 05:38

@vharseko vharseko left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed PR #1222 at 9fe0246, including the PR description and the scope defined in #1221 and #1075.

No actionable findings. The added fixtures and assertions are consistent with the current Restlet handlers, and the per-spec client avoids interference with the existing OAuth2/UMA clients.

Validation: both changed JavaScript files pass node --check, and git diff --check is clean. I inspected the build-docker job log and confirmed that all 11 OIDC tests passed in the shared run (90 passed, 1 unrelated test skipped). I did not rerun live e2e locally because no OpenAM container was running.

@maximthomas
maximthomas merged commit 3b050dc into OpenIdentityPlatform:master Nov 6, 2026
16 checks passed
@maximthomas
maximthomas deleted the features/1075-e2e-oidc-discovery branch November 6, 2026 07:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement javascript Pull requests that update Javascript code oauth2 OAuth2 / OpenID Connect tests Test suite: coverage, fixtures, or test infrastructure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants