Skip to content

Add private_key_jwt integration tests for /oauth2/token - #4853

Merged
sahandilshan merged 1 commit into
thunder-id:mainfrom
sahandilshan:integration-tests
Aug 13, 2026
Merged

Add private_key_jwt integration tests for /oauth2/token#4853
sahandilshan merged 1 commit into
thunder-id:mainfrom
sahandilshan:integration-tests

Conversation

@sahandilshan

@sahandilshan sahandilshan commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add 20 integration tests exercising private_key_jwt client authentication at the /oauth2/token endpoint, filling a gap where zero integration tests actually POSTed a client_assertion to obtain a real token
  • Export RandomJTI() and SignJWT() helpers in testutils/oauth2_utils.go for JWT assertion construction

Test coverage

Positive (5 tests): RS256, ES256, PS256 success paths, client_id in body alongside assertion, scope parameter handling

Negative — format (5 tests): Missing client_assertion, missing client_assertion_type, unsupported assertion type, malformed JWT, empty sub claim

Negative — validation (7 tests): Wrong signing key, expired assertion, wrong audience, array audience (FAPI 2.0 rejection), missing jti, replayed assertion (JTI replay protection), kid not matching JWKS

Negative — auth method conflicts (3 tests): client_id mismatch, Basic auth + assertion, client_secret + assertion

Test plan

  • All 20 tests pass against live integration test server (make test_integration PACKAGE="./oauth/token" RUN="TestPrivateKeyJWTTestSuite")
  • go build ./... and go vet ./... pass
  • make lint passes
  • Verify no regressions in existing token endpoint tests

Summary by CodeRabbit

  • Tests
    • Added comprehensive integration coverage for OAuth private_key_jwt client authentication.
    • Verified successful authentication with RS256, ES256, and PS256 signatures, scopes, and client IDs in request bodies.
    • Added validation for malformed, expired, replayed, mismatched, incorrectly signed, and unsupported authentication requests.
    • Added coverage confirming rejection of conflicting Basic Authentication and client-secret credentials.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6ea18398-69d4-4b57-86fd-b3e8d48ec16c

📥 Commits

Reviewing files that changed from the base of the PR and between 668c171 and c02e388.

📒 Files selected for processing (2)
  • tests/integration/oauth/token/private_key_jwt_test.go
  • tests/integration/testutils/oauth2_utils.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/integration/testutils/oauth2_utils.go
  • tests/integration/oauth/token/private_key_jwt_test.go

📝 Walkthrough

Walkthrough

Added an integration suite for OAuth private_key_jwt authentication. The suite covers RSA, EC, and PS signing, successful token requests, invalid assertions, replay protection, key validation, and conflicting authentication methods. It also exports JWT test helper wrappers.

Changes

Private key JWT authentication

Layer / File(s) Summary
Fixtures and assertion flow
tests/integration/oauth/token/private_key_jwt_test.go, tests/integration/testutils/oauth2_utils.go
Added RSA, EC, and PS application fixtures, suite lifecycle handling, JWT assertion signing, token-request construction, response assertions, and exported JWT helper wrappers.
Successful token flows
tests/integration/oauth/token/private_key_jwt_test.go
Added successful token tests for RS256, ES256, PS256, body client IDs, and scopes.
Authentication validation
tests/integration/oauth/token/private_key_jwt_test.go
Added rejection tests for malformed or invalid assertions, expired and replayed assertions, invalid audiences, unknown keys, mismatched client IDs, Basic Auth, and client secrets.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TestSuite
  participant TokenEndpoint
  participant JWKS
  TestSuite->>TestSuite: Sign client assertion
  TestSuite->>TokenEndpoint: Submit token request
  TokenEndpoint->>JWKS: Resolve signing key
  JWKS-->>TokenEndpoint: Return registered key
  TokenEndpoint-->>TestSuite: Return token or OAuth error
Loading

Possibly related PRs

Suggested reviewers: thamindudilshan

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the addition of private_key_jwt integration tests for the /oauth2/token endpoint.
Description check ✅ Passed The description explains the purpose, test coverage, and verification results; it omits the template's Approach and Security checks sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sahandilshan sahandilshan added Test skip-changelog Skip generating changelog for a particular PR labels Aug 11, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (4)
tests/integration/oauth/token/private_key_jwt_test.go (4)

663-671: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The explicit jti is redundant.

createClientAssertion already generates a random jti when opts.jti is empty. Both requests reuse the same assertion string, so replay detection is exercised either way.

♻️ Proposed simplification
 	assertion := createClientAssertion(clientAssertionOptions{
 		sub: pkjRSAClientID,
 		aud: pkjIssuer,
 		kid: pkjRSAKid,
 		alg: "RS256",
 		key: ts.rsaKey.Private,
-		jti: testutils.RandomJTI(),
 	})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/oauth/token/private_key_jwt_test.go` around lines 663 -
671, Remove the explicit jti assignment from the createClientAssertion options
in TestReplayedAssertion, relying on its built-in random JTI generation while
preserving the assertion reuse and replay-detection behavior.

22-33: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Verify that a shared base URL constant already exists in testutils.

pkjServerURL and pkjIssuer hardcode https://localhost:8095. Other integration suites likely declare the same value. If testutils exports a shared constant, reuse it instead.

#!/bin/bash
# Description: Find existing declarations of the integration server base URL.
rg -n -C2 'localhost:8095' tests/integration --type=go | head -60
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/oauth/token/private_key_jwt_test.go` around lines 22 - 33,
Check testutils for an exported shared integration server base URL constant and
reuse it for pkjServerURL and pkjIssuer if available, removing the duplicated
https://localhost:8095 literals while preserving the issuer and server URL
values.

510-517: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

TestEmptySub also clears iss.

createClientAssertion sets iss from opts.sub at Line 236. When sub is empty, both sub and iss are empty. The test therefore does not isolate the empty-sub condition. The server may reject the assertion on the missing iss claim instead.

Add an explicit iss option, or rename the test to reflect that both claims are empty.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/oauth/token/private_key_jwt_test.go` around lines 510 -
517, Update TestEmptySub and its createClientAssertion options so iss remains
populated while sub is empty, isolating the intended empty-sub condition;
alternatively, rename the test to explicitly cover both empty claims.

201-213: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the unused tamperSig field and its branch. No test or other Go file sets tamperSig, so the branch is unreachable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/oauth/token/private_key_jwt_test.go` around lines 201 -
213, Remove the unused tamperSig field from clientAssertionOptions and delete
the corresponding tamperSig-dependent branch in createClientAssertion. Leave the
remaining JWT option handling unchanged.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/integration/oauth/token/private_key_jwt_test.go`:
- Around line 663-671: Remove the explicit jti assignment from the
createClientAssertion options in TestReplayedAssertion, relying on its built-in
random JTI generation while preserving the assertion reuse and replay-detection
behavior.
- Around line 22-33: Check testutils for an exported shared integration server
base URL constant and reuse it for pkjServerURL and pkjIssuer if available,
removing the duplicated https://localhost:8095 literals while preserving the
issuer and server URL values.
- Around line 510-517: Update TestEmptySub and its createClientAssertion options
so iss remains populated while sub is empty, isolating the intended empty-sub
condition; alternatively, rename the test to explicitly cover both empty claims.
- Around line 201-213: Remove the unused tamperSig field from
clientAssertionOptions and delete the corresponding tamperSig-dependent branch
in createClientAssertion. Leave the remaining JWT option handling unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: aba67abd-e64f-4d19-a359-21ef2877ac18

📥 Commits

Reviewing files that changed from the base of the PR and between f80ec6e and fb470f6.

📒 Files selected for processing (2)
  • tests/integration/oauth/token/private_key_jwt_test.go
  • tests/integration/testutils/oauth2_utils.go

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@sahandilshan sahandilshan added the trigger-pr-builder Add when the PR is ready for CI; starts the PR Builder for this and all later pushes label Aug 12, 2026
@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Signed-off-by: Sahan Dilshan <sahandilshan222@gmail.com>
@sahandilshan
sahandilshan added this pull request to the merge queue Aug 13, 2026
Merged via the queue into thunder-id:main with commit b45e8c4 Aug 13, 2026
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-changelog Skip generating changelog for a particular PR Test trigger-pr-builder Add when the PR is ready for CI; starts the PR Builder for this and all later pushes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants