Skip to content

Conversation

@alepane21
Copy link
Contributor

@alepane21 alepane21 commented Dec 23, 2025

By default, we force to check that the jwks keys have the use param set to "sig". By standard the value is optional and it could also be left empty.
With this PR I introduce two changes:

  • make the jwt validation errors more visible when logging debug level messages (nothing changes on the graphql error description)
  • allow to configure in the router the allowed "use" values

Summary by CodeRabbit

  • New Features
    • Added configuration to specify allowed JWKS "use" values and propagated it through authentication and JWKS test server customization.
  • Bug Fixes
    • Masked internal error details for unauthorized authentication responses and standardized unauthorized error payloads.
  • Tests
    • Added tests covering JWKS "use" handling to validate authentication behavior for allowed/denied use scenarios.
  • Documentation
    • Schema and testdata updated to include the new allowed_use configuration option.

✏️ Tip: You can customize this high-level summary in your review settings.

Checklist

  • I have discussed my proposed changes in an issue and have received approval to proceed.
  • I have followed the coding standards of the project.
  • Tests or benchmarks have been added or updated.
  • Documentation has been updated on https://github.com/wundergraph/cosmo-docs.
  • I have read the Contributors Guide.

@coderabbitai
Copy link

coderabbitai bot commented Dec 23, 2025

Walkthrough

Adds JWK "use" customization end-to-end: new JWKS server options, JWK marshalling with configurable use, AllowedUse propagation into JWKSConfig and token decoder, and unauthorized error message masking in GraphQL and websocket paths. Tests and config schema updated to exercise the new behavior.

Changes

Cohort / File(s) Summary
JWKS crypto & server options
router-tests/jwks/crypto.go, router-tests/jwks/jwks.go, router-tests/cmd/jwks-server/main.go
New MarshalJWKWithUse(use jwkset.USE) on Crypto implementations; preserved MarshalJWK() delegating to default. Added ServerOption API (WithUse, WithProviders), serverConfig, and NewServerWithOptions(); JWKS marshalling now uses configured use. Import reordering in jwks-server main (cosmetic).
Token decoder & allowed-use handling
router/pkg/authentication/jwks_token_decoder.go
Added AllowedUse []string on JWKSConfig; introduced getUseWhitelist() mapping to jwkset.USE; createKeyFunc accepts and applies a use whitelist; entries now carry allowedUse.
Configuration & schema changes
router/pkg/config/config.go, router/pkg/config/config.schema.json, router/pkg/config/testdata/config_full.json
Added AllowedUse []string to JWKSConfiguration, added allowed_use schema (enum: "sig","enc",""), and updated testdata JWKS entries to include AllowedUse fields (null).
Authentication wiring
router/core/supervisor_instance.go
Propagates AllowedUse from JWKS config into authentication.JWKSConfig and token decoder construction.
Error handling / masking
router/core/access_controller.go, router/core/graphql_prehandler.go, router/core/websocket.go
On authentication failures, wrap/join underlying error with ErrUnauthorized; GraphQL and websocket flows now mask or forward ErrUnauthorized explicitly so error payloads do not leak underlying details.
Tests
router-tests/authentication_test.go
Added TestUseCustomization suite that spins up JWKS test server with WithUse/WithProviders, exercises allowed-use empty-string vs default behaviors, and asserts auth success/failure and headers.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes both main changes: JWT validation error logging improvements and JWKS 'use' parameter customization to allow values beyond 'sig'.

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8fc525c and 3f8dc80.

📒 Files selected for processing (1)
  • router/pkg/config/config.schema.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • router/pkg/config/config.schema.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: build-router
  • GitHub Check: build_test
  • GitHub Check: image_scan (nonroot)
  • GitHub Check: build_push_image
  • GitHub Check: integration_test (./events)
  • GitHub Check: integration_test (./telemetry)
  • GitHub Check: image_scan
  • GitHub Check: build_push_image (nonroot)
  • GitHub Check: integration_test (./. ./fuzzquery ./lifecycle ./modules)
  • GitHub Check: Analyze (go)
  • GitHub Check: Analyze (javascript-typescript)

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

@github-actions
Copy link

github-actions bot commented Dec 23, 2025

Router-nonroot image scan passed

✅ No security vulnerabilities found in image:

ghcr.io/wundergraph/cosmo/router:sha-58c3229fbd9f7890293bde04679e9b7e0d6d0042-nonroot

@codecov
Copy link

codecov bot commented Dec 23, 2025

Codecov Report

❌ Patch coverage is 95.83333% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 60.85%. Comparing base (98bb8e9) to head (6c1c4cf).

Files with missing lines Patch % Lines
router/core/supervisor_instance.go 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2431      +/-   ##
==========================================
- Coverage   62.75%   60.85%   -1.90%     
==========================================
  Files         296      229      -67     
  Lines       41353    23830   -17523     
  Branches     4244        0    -4244     
==========================================
- Hits        25951    14502   -11449     
+ Misses      15381     8061    -7320     
- Partials       21     1267    +1246     
Files with missing lines Coverage Δ
router/core/access_controller.go 79.31% <100.00%> (ø)
router/core/graphql_prehandler.go 80.38% <100.00%> (ø)
router/core/websocket.go 75.46% <100.00%> (ø)
router/pkg/authentication/jwks_token_decoder.go 84.24% <100.00%> (ø)
router/pkg/config/config.go 63.63% <ø> (ø)
router/core/supervisor_instance.go 0.00% <0.00%> (ø)

... and 519 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@alepane21 alepane21 changed the title feat: improve logging of auth errors feat: improve logging of authorization errors and allow use customization Dec 24, 2025
@alepane21 alepane21 marked this pull request as ready for review December 24, 2025 17:19
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
router/core/websocket.go (1)

399-410: Consider adding debug logging for the original error.

The error sanitization correctly prevents leaking internal error details to clients. However, unlike handleAuthenticationFailure in graphql_prehandler.go (which logs the original error at debug level on line 1143), this path silently discards the underlying error details. Consider adding a debug log to aid troubleshooting:

🔎 Suggested improvement
 			if h.accessController != nil {
 				handler.request, err = h.accessController.Access(w, r)
 				if err != nil {
 					statusCode := http.StatusForbidden
 					errorMessage := err
 					if errors.Is(err, ErrUnauthorized) {
 						statusCode = http.StatusUnauthorized
 						errorMessage = ErrUnauthorized
 					}
+					requestLogger.Debug("Failed to authenticate websocket connection from initial payload", zap.Error(err))
 					http.Error(handler.w, http.StatusText(statusCode), statusCode)
 					_ = handler.writeErrorMessage(requestID, errorMessage)
 					handler.Close(false)
 					return
 				}
 			}
router/pkg/authentication/jwks_token_decoder.go (2)

255-265: Consider clarifying the behavior for empty slice vs. nil.

The function returns [UseSig] when allowedUse is nil, but returns an empty slice when allowedUse is []string{}. This distinction might be confusing:

  • nil[UseSig] (default, restricts to "sig" use)
  • []string{} (empty slice) → [] (empty whitelist, might block all keys)
  • []string{""}[USE("")] (allows empty "use")

While this appears intentional for the use case, consider either:

  1. Treating empty slice the same as nil (return default), or
  2. Adding a comment explaining this behavior
💡 Optional: Treat empty slice as default
 func getUseWhitelist(allowedUse []string) []jwkset.USE {
-	if allowedUse == nil {
+	if len(allowedUse) == 0 {
 		return []jwkset.USE{jwkset.UseSig}
 	}
 
 	useWhitelist := make([]jwkset.USE, len(allowedUse))
 	for i, u := range allowedUse {
 		useWhitelist[i] = jwkset.USE(u)
 	}
 	return useWhitelist
 }

48-48: Add validation for AllowedUse values in getUseWhitelist().

The AllowedUse strings are cast directly to jwkset.USE without validation. While the jwkset library provides IANARegistered() for validation, the application should validate against the known USE values ("sig" and "enc" per RFC 7517) before passing them downstream. This prevents invalid values from propagating to the jwkset library.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8237c8d and 8fc525c.

📒 Files selected for processing (12)
  • router-tests/authentication_test.go
  • router-tests/cmd/jwks-server/main.go
  • router-tests/jwks/crypto.go
  • router-tests/jwks/jwks.go
  • router/core/access_controller.go
  • router/core/graphql_prehandler.go
  • router/core/supervisor_instance.go
  • router/core/websocket.go
  • router/pkg/authentication/jwks_token_decoder.go
  • router/pkg/config/config.go
  • router/pkg/config/config.schema.json
  • router/pkg/config/testdata/config_full.json
🧰 Additional context used
🧠 Learnings (10)
📓 Common learnings
Learnt from: SkArchon
Repo: wundergraph/cosmo PR: 2067
File: router/pkg/authentication/jwks_token_decoder.go:80-106
Timestamp: 2025-07-21T14:46:34.879Z
Learning: In the Cosmo router project, required field validation for JWKS configuration (Secret, Algorithm, KeyId) is handled at the JSON schema level in config.schema.json rather than through runtime validation in the Go code at router/pkg/authentication/jwks_token_decoder.go.
📚 Learning: 2025-07-21T14:46:34.879Z
Learnt from: SkArchon
Repo: wundergraph/cosmo PR: 2067
File: router/pkg/authentication/jwks_token_decoder.go:80-106
Timestamp: 2025-07-21T14:46:34.879Z
Learning: In the Cosmo router project, required field validation for JWKS configuration (Secret, Algorithm, KeyId) is handled at the JSON schema level in config.schema.json rather than through runtime validation in the Go code at router/pkg/authentication/jwks_token_decoder.go.

Applied to files:

  • router-tests/cmd/jwks-server/main.go
  • router/pkg/config/config.schema.json
  • router/pkg/authentication/jwks_token_decoder.go
  • router/core/supervisor_instance.go
  • router/pkg/config/testdata/config_full.json
  • router-tests/jwks/jwks.go
  • router/pkg/config/config.go
📚 Learning: 2025-11-19T15:13:57.821Z
Learnt from: dkorittki
Repo: wundergraph/cosmo PR: 2273
File: router/core/graphql_handler.go:0-0
Timestamp: 2025-11-19T15:13:57.821Z
Learning: In the Cosmo router (wundergraph/cosmo), error handling follows a two-phase pattern: (1) Prehandler phase handles request parsing, validation, and setup errors using `httpGraphqlError` and `writeOperationError` (in files like graphql_prehandler.go, operation_processor.go, parse_multipart.go, batch.go); (2) Execution phase handles resolver execution errors using `WriteError` in GraphQLHandler.ServeHTTP. Because all `httpGraphqlError` instances are caught in the prehandler before ServeHTTP is invoked, any error type checks for `httpGraphqlError` in the execution-phase WriteError method are unreachable code.

Applied to files:

  • router/core/graphql_prehandler.go
📚 Learning: 2025-07-21T15:06:36.664Z
Learnt from: SkArchon
Repo: wundergraph/cosmo PR: 2067
File: router/pkg/config/config.schema.json:1637-1644
Timestamp: 2025-07-21T15:06:36.664Z
Learning: In the Cosmo router project, when extending JSON schema validation for security-sensitive fields like JWKS secrets, backwards compatibility is maintained by implementing warnings in the Go code rather than hard validation constraints in the schema. This allows existing configurations to continue working while alerting users to potential security issues.

Applied to files:

  • router/pkg/config/config.schema.json
  • router/pkg/config/testdata/config_full.json
  • router/pkg/config/config.go
📚 Learning: 2025-09-17T18:13:31.021Z
Learnt from: SkArchon
Repo: wundergraph/cosmo PR: 2224
File: router/pkg/authentication/jwks_token_decoder.go:176-199
Timestamp: 2025-09-17T18:13:31.021Z
Learning: In router/pkg/authentication/jwks_token_decoder.go, when AllowEmptyAlgorithm is true, the allowedAlgorithms slice is populated by getSupportedAlgorithms() from validation_store.go, ensuring it's never empty and preventing unintended token rejections.

Applied to files:

  • router/pkg/config/config.schema.json
  • router/pkg/authentication/jwks_token_decoder.go
  • router/core/supervisor_instance.go
  • router/pkg/config/testdata/config_full.json
  • router/pkg/config/config.go
📚 Learning: 2025-09-17T18:13:31.021Z
Learnt from: SkArchon
Repo: wundergraph/cosmo PR: 2224
File: router/pkg/authentication/jwks_token_decoder.go:176-199
Timestamp: 2025-09-17T18:13:31.021Z
Learning: In router/pkg/authentication/jwks_token_decoder.go, when AllowEmptyAlgorithm is true, the allowedAlgorithms slice is populated by getSupportedAlgorithms() from validation_store.go, ensuring it's never empty and preventing unintended token rejections. The validation store maintains its own internal supported algorithms map that gets returned regardless of input parameters.

Applied to files:

  • router/pkg/config/config.schema.json
  • router/pkg/authentication/jwks_token_decoder.go
  • router/core/supervisor_instance.go
  • router/pkg/config/testdata/config_full.json
  • router/pkg/config/config.go
📚 Learning: 2025-08-20T10:08:17.857Z
Learnt from: endigma
Repo: wundergraph/cosmo PR: 2155
File: router/core/router.go:1857-1866
Timestamp: 2025-08-20T10:08:17.857Z
Learning: In the Cosmo router codebase, JSON schema validation prevents null values in TrafficShapingRules subgraph configurations, making nil checks unnecessary when dereferencing subgraph rule pointers in NewSubgraphTransportOptions.

Applied to files:

  • router/pkg/config/config.schema.json
📚 Learning: 2025-08-20T10:08:17.857Z
Learnt from: endigma
Repo: wundergraph/cosmo PR: 2155
File: router/core/router.go:1857-1866
Timestamp: 2025-08-20T10:08:17.857Z
Learning: router/pkg/config/config.schema.json forbids null values for traffic_shaping.subgraphs: additionalProperties references $defs.traffic_shaping_subgraph_request_rule with type "object". Therefore, in core.NewSubgraphTransportOptions, dereferencing each subgraph rule pointer is safe under schema-validated configs, and a nil-check is unnecessary.

Applied to files:

  • router/pkg/config/config.schema.json
  • router/pkg/config/testdata/config_full.json
📚 Learning: 2025-09-29T10:28:07.122Z
Learnt from: dkorittki
Repo: wundergraph/cosmo PR: 2192
File: router/pkg/config/config.go:1028-1029
Timestamp: 2025-09-29T10:28:07.122Z
Learning: The deprecation strategy for IntrospectionEnabled field in router/pkg/config/config.go is to first mark it as deprecated, then migrate all call sites to use IntrospectionConfig.Enabled, and finally remove the deprecated field. The envDefault tag should be kept during the deprecation period for backward compatibility.

Applied to files:

  • router/pkg/config/config.go
📚 Learning: 2025-08-28T09:59:19.999Z
Learnt from: SkArchon
Repo: wundergraph/cosmo PR: 2167
File: router/core/supervisor_instance.go:196-204
Timestamp: 2025-08-28T09:59:19.999Z
Learning: In router/pkg/config/config.go, the BackoffJitterRetry struct uses envDefault tags to provide default values for Algorithm ("backoff_jitter") and Expression ("IsRetryableStatusCode() || IsConnectionError() || IsTimeout()"), ensuring that even when YAML configs omit these fields, valid defaults are applied automatically, preventing ProcessRetryOptions from rejecting empty values.

Applied to files:

  • router/pkg/config/config.go
🧬 Code graph analysis (3)
router/core/graphql_prehandler.go (2)
router/core/access_controller.go (1)
  • ErrUnauthorized (16-16)
router/pkg/pubsub/datasource/error.go (1)
  • Error (3-6)
router/core/websocket.go (1)
router/core/access_controller.go (1)
  • ErrUnauthorized (16-16)
router-tests/jwks/jwks.go (2)
router-tests/jwks/crypto.go (1)
  • Crypto (17-23)
router-tests/cmd/jwks-server/main.go (1)
  • NewServerWithCrypto (214-252)
🔇 Additional comments (9)
router-tests/jwks/crypto.go (1)

17-23: LGTM! Clean interface extension.

The new MarshalJWKWithUse method is properly added to the interface, and existing implementations correctly delegate MarshalJWK() to the new method with the default jwkset.UseSig value, maintaining backward compatibility.

router-tests/cmd/jwks-server/main.go (1)

52-56: LGTM! Import reorganization only.

No functional changes; imports were simply reordered for consistency.

router/core/access_controller.go (1)

51-53: LGTM! Good error composition pattern.

Using errors.Join(err, ErrUnauthorized) preserves the underlying authentication error for debugging and logging while ensuring errors.Is(err, ErrUnauthorized) still returns true. This enables the sanitization logic in graphql_prehandler.go and websocket.go to mask internal error details from clients.

router/pkg/config/testdata/config_full.json (1)

489-542: LGTM! Test data updated to reflect new schema.

The AllowedUse field is correctly added to each JWKS entry with null values, which will exercise default behavior during testing.

router/core/graphql_prehandler.go (1)

1149-1158: LGTM! Proper error sanitization for security.

The implementation correctly sanitizes error details in the GraphQL response while preserving the full error for logging (line 1143), request context (line 1142), and observability spans (lines 1146-1147). This prevents leaking sensitive authentication failure details to clients.

router/pkg/config/config.go (1)

486-501: LGTM! AllowedUse field added correctly.

The AllowedUse []string field is properly added to JWKSConfiguration. Based on learnings, validation for JWKS configuration fields is handled at the JSON schema level rather than runtime validation in Go code.

router/core/supervisor_instance.go (1)

290-309: LGTM! Configuration propagation is correct.

The AllowedUse field is properly propagated from the JWKS configuration to the authentication.JWKSConfig, consistent with how other JWKS settings are passed through.

router-tests/authentication_test.go (1)

4019-4134: LGTM! Well-structured test for the new "use" customization feature.

The test properly validates both the default behavior (rejecting tokens when JWK has empty "use" field) and the customized behavior (allowing empty "use" when explicitly configured). The helper functions follow existing patterns and make the test readable.

router-tests/jwks/jwks.go (1)

136-215: LGTM! Clean refactoring to options-based initialization.

The introduction of the options pattern is idiomatic and maintains backward compatibility. Key improvements:

  1. Backward compatibility: NewServerWithCrypto still works via delegation
  2. Flexibility: Options allow customizing "use" parameter without breaking existing code
  3. Good defaults: Default use is UseSig, preserving previous behavior
  4. Validation: Checks that at least one provider is specified

The refactoring properly supports the new JWKS "use" customization feature while keeping the API clean.

alepane21 and others added 3 commits December 24, 2025 18:26
…401-on-router-with-valid-token' into ale/eng-8704-jwt-authentication-401-on-router-with-valid-token

func createKeyFunc(ctx context.Context, options jwkset.HTTPClientOptions) (keyfunc.Keyfunc, error) {
func getUseWhitelist(allowedUse []string) []jwkset.USE {
if allowedUse == nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

Hypothetically allowedUse could also be an empty slice. len returns 0 for both, empty slices and nil.

Suggested change
if allowedUse == nil {
if len(allowedUse) == 0 {

}

func createKeyFunc(ctx context.Context, options jwkset.HTTPClientOptions) (keyfunc.Keyfunc, error) {
func getUseWhitelist(allowedUse []string) []jwkset.USE {
Copy link
Contributor

Choose a reason for hiding this comment

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

Function name is a bit wonky imo. Given that jwks.USE is defined as

USE is a set of "JSON Web Key Use" types

How about toKeyUseTypes ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants