Skip to content

feat: support symmetric key algorithms for JWKs#2067

Merged
SkArchon merged 9 commits into
mainfrom
milinda/eng-7583-support-symmetric-key-algorithm-for-jwk
Jul 23, 2025
Merged

feat: support symmetric key algorithms for JWKs#2067
SkArchon merged 9 commits into
mainfrom
milinda/eng-7583-support-symmetric-key-algorithm-for-jwk

Conversation

@SkArchon

@SkArchon SkArchon commented Jul 21, 2025

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

This PR adds the ability to use secrets for authorization.

Note: I used the same configuration section where "url", "algorithms", etc is present, instead of moving them into their own section (as this would be breaking backwards compatibility, and I noticed we have already done this in the recent past). However, the schema json validates that "url" and "secret" cannot be specified together.

Summary by CodeRabbit

  • New Features

    • Added support for configuring JWT authentication using local secrets with specified algorithms and key IDs, alongside existing remote JWKS URL support.
    • Configuration schema and validation updated to enforce exclusive use of either remote URL or local secret-based JWKS entries.
  • Bug Fixes

    • Improved validation for JWKS configuration to prevent invalid combinations of settings.
  • Tests

    • Enhanced and expanded test coverage for both remote and local secret-based JWKS authentication scenarios and configuration validation.

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

coderabbitai Bot commented Jul 21, 2025

Copy link
Copy Markdown
Contributor

"""

Walkthrough

The changes introduce support for secret-based JWKS (JSON Web Key Set) authentication alongside existing HTTP-based JWKS. This involves extending configuration structures and schemas, updating token decoder logic to handle both remote and local secret keys, and adding comprehensive tests for mixed and secret-based JWKS authentication scenarios. Supporting utilities and configuration validation are also updated.

Changes

Files/Paths Change Summary
router-tests/authentication_test.go Renamed test; added subtests for multiple JWKS and secret-based JWKS scenarios; added helper for HS256 tokens.
router-tests/utils.go Refactored auth configuration; added helper to configure authenticators from JWKS configs.
router/core/supervisor_instance.go Added Secret, Algorithm, and KeyId fields to JWKSConfig initialization.
router/pkg/authentication/jwks_token_decoder.go Extended JWKSConfig with Secret, Algorithm, KeyId; updated decoder to support secret-based (local) JWKs.
router/pkg/config/config.go Added Secret, Algorithm, KeyId fields to JWKSConfiguration struct.
router/pkg/config/config.schema.json Extended JWT JWKS schema: added secret, algorithm, key_id; enforced mutual exclusivity with oneOf constraints.
router/pkg/config/config_test.go Added tests validating JWKS configuration schema for secret and URL cases (valid and invalid).
router/pkg/config/testdata/config_full.json Added Secret, Algorithm, KeyId fields (empty) to all JWKS entries in test config; updated one algorithm value.
router/pkg/config/fixtures/full.yaml Updated JWKS algorithms list: replaced HS256 with ES256 for one JWKS URL.

Estimated code review effort

3 (~45 minutes)
"""


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 959015b and d11b619.

📒 Files selected for processing (2)
  • router/pkg/config/fixtures/full.yaml (1 hunks)
  • router/pkg/config/testdata/config_full.json (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • router/pkg/config/fixtures/full.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • router/pkg/config/testdata/config_full.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: integration_test (./telemetry)
  • GitHub Check: integration_test (./. ./fuzzquery ./lifecycle ./modules)
  • GitHub Check: image_scan (nonroot)
  • GitHub Check: image_scan
  • GitHub Check: integration_test (./events)
  • GitHub Check: build_push_image
  • GitHub Check: build_push_image (nonroot)
  • GitHub Check: build_test
  • GitHub Check: Analyze (go)
  • GitHub Check: Analyze (javascript-typescript)
✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch milinda/eng-7583-support-symmetric-key-algorithm-for-jwk

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions

github-actions Bot commented Jul 21, 2025

Copy link
Copy Markdown

Router image scan passed

✅ No security vulnerabilities found in image:

ghcr.io/wundergraph/cosmo/router:sha-f003c285b83859586d31b042c1abeb0400dd2e4d

@github-actions

Copy link
Copy Markdown

Router-nonroot image scan passed

✅ No security vulnerabilities found in image:

ghcr.io/wundergraph/cosmo/router:sha-fb1da1cd4b02165cca0b07c199a8745905bfc168-nonroot

@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.

Actionable comments posted: 2

🔭 Outside diff range comments (1)
router/pkg/authentication/jwks_token_decoder.go (1)

41-49: Add documentation for the new fields and clarify mutual exclusivity.

The new fields lack documentation explaining their purpose and usage. Additionally, the mutual exclusivity between URL-based and secret-based configurations should be documented at the struct level.

Add documentation to clarify the usage:

 type JWKSConfig struct {
+	// URL specifies the HTTP endpoint to fetch JWK sets from.
+	// Either URL or Secret must be provided, but not both.
 	URL               string
 	RefreshInterval   time.Duration
 	AllowedAlgorithms []string
 
+	// Secret is the symmetric key for HMAC-based JWT validation.
+	// When provided, Algorithm and KeyId must also be specified.
 	Secret    string
+	// Algorithm specifies the signing algorithm (e.g., HS256, HS384, HS512).
+	// Required when Secret is provided.
 	Algorithm string
+	// KeyId is the key identifier for the secret-based JWK.
+	// Required when Secret is provided.
 	KeyId     string
 }
🧹 Nitpick comments (7)
router/pkg/config/config_test.go (1)

1202-1284: Well-structured test with good coverage of JWKS configuration scenarios.

The test function effectively validates the new secret-based JWKS authentication feature with comprehensive subtests covering both valid and invalid configurations.

Minor suggestions for improvement:

  1. Line 1244: Consider using a realistic algorithm array instead of empty array:
-        algorithms: []
+        algorithms: ["RS256"]
  1. Test naming consistency: Consider using more descriptive test names that clearly indicate the expected behavior, e.g., "should_reject_mixed_url_and_secret_config".
router/pkg/config/config.schema.json (2)

1642-1653: Incorrect description for key_id.

Line 1652 still reads “The secret of the JWKs”, copy-pasted from the secret field. This should describe the key identifier instead, otherwise generated docs (and UX) will mislead users.

 "key_id": {
   "type": "string",
-  "description": "The secret of the JWKs"
+  "description": "Key ID (kid) that will be set on the synthesized JWK"
 }

1685-1694: One-of guard blocks look good – just ask for self-documentation.

The new mutual-exclusivity rules are well-formed; however, adding a brief comment above each branch clarifying “remote JWKS via URL” vs “local shared-secret JWKS” would aid future maintainers reading this ~3 000-line schema.

router/pkg/authentication/jwks_token_decoder.go (1)

97-97: Consider adding validation for secret strength.

The code accepts any string as a secret without validating its strength. For HMAC algorithms, weak secrets can compromise security.

Consider adding a minimum length validation for secrets:

 	}
+	// Validate secret strength based on algorithm
+	minSecretLength := 32 // 256 bits minimum
+	if alg == jwkset.AlgHS384 {
+		minSecretLength = 48 // 384 bits
+	} else if alg == jwkset.AlgHS512 {
+		minSecretLength = 64 // 512 bits
+	}
+	if len(c.Secret) < minSecretLength {
+		return nil, fmt.Errorf("secret length (%d bytes) is below recommended minimum (%d bytes) for algorithm %s", 
+			len(c.Secret), minSecretLength, c.Algorithm)
+	}
 	jwk, err := jwkset.NewJWKFromKey([]byte(c.Secret), jwkOptions)
router-tests/authentication_test.go (3)

757-804: LGTM! Consider adding test coverage for tokens from different sources.

The test correctly validates authentication with multiple JWKS configurations. However, it only tests with a token from authServer2.

Consider extending the test to verify tokens from all three sources work correctly:

 	t.Run("authenticate when multiple jwks are present", func(t *testing.T) {
 		authServer1, err := jwks.NewServer(t)
 		t.Cleanup(authServer1.Close)
 		require.NoError(t, err)
 
 		authServer2, err := jwks.NewServer(t)
 		t.Cleanup(authServer2.Close)
 		require.NoError(t, err)
 
-		token, err := authServer2.Token(nil)
+		token1, err := authServer1.Token(nil)
+		require.NoError(t, err)
+		token2, err := authServer2.Token(nil)
+		require.NoError(t, err)
 
 		t.Parallel()
 
+		secret := "example secret"
+		kid := "givenKID"
 		authenticators := ConfigureAuthWithJwksConfig(t, []authentication.JWKSConfig{
 			{
-				Secret:    "example secret",
+				Secret:    secret,
 				Algorithm: string(jwkset.AlgHS256),
-				KeyId:     "givenKID",
+				KeyId:     kid,
 			},
 			{
 				URL:             authServer1.JWKSURL(),
 				RefreshInterval: time.Second * 5,
 			},
 			{
 				URL:             authServer2.JWKSURL(),
 				RefreshInterval: time.Second * 5,
 			},
 		})
+		
+		token3 := generateToken(t, kid, secret, jwt.SigningMethodHS256)
 
 		testenv.Run(t, &testenv.Config{
 			RouterOptions: []core.Option{
 				core.WithAccessController(core.NewAccessController(authenticators, true)),
 			},
 		}, func(t *testing.T, xEnv *testenv.Environment) {
-			// Operations with a token should succeed
-			header := http.Header{
-				"Authorization": []string{"Bearer " + token},
+			// Test all three token sources
+			for _, token := range []string{token1, token2, token3} {
+				header := http.Header{
+					"Authorization": []string{"Bearer " + token},
+				}
+				res, err := xEnv.MakeRequest(http.MethodPost, "/graphql", header, strings.NewReader(employeesQuery))
+				require.NoError(t, err)
+				defer res.Body.Close()
+				require.Equal(t, http.StatusOK, res.StatusCode)
+				require.Equal(t, JwksName, res.Header.Get(xAuthenticatedByHeader))
+				data, err := io.ReadAll(res.Body)
+				require.NoError(t, err)
+				require.Equal(t, employeesExpectedData, string(data))
 			}
-			res, err := xEnv.MakeRequest(http.MethodPost, "/graphql", header, strings.NewReader(employeesQuery))
-			require.NoError(t, err)
-			defer res.Body.Close()
-			require.Equal(t, http.StatusOK, res.StatusCode)
-			require.Equal(t, JwksName, res.Header.Get(xAuthenticatedByHeader))
-			data, err := io.ReadAll(res.Body)
-			require.NoError(t, err)
-			require.Equal(t, employeesExpectedData, string(data))
 		})
 	})

2038-2044: Make the signing method parameter more flexible.

The function parameter is currently typed as *jwt.SigningMethodHMAC, but it could be more generic to support all signing methods.

-func generateToken(t *testing.T, kid string, secret string, signingMethod *jwt.SigningMethodHMAC) string {
+func generateToken(t *testing.T, kid string, secret string, signingMethod jwt.SigningMethod) string {
 	token := jwt.New(signingMethod)
 	token.Header[jwkset.HeaderKID] = kid
 	jwtValue, err := token.SignedString([]byte(secret))
 	require.NoError(t, err)
 	return jwtValue
 }

808-924: Consider adding tests for other HMAC algorithms and edge cases.

The current tests focus on HS256. Consider adding test coverage for:

  • HS384 and HS512 algorithms
  • Invalid/unsupported algorithms
  • Empty or weak secrets
  • Missing required fields (Algorithm or KeyId when Secret is provided)

Would you like me to generate additional test cases for these scenarios?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ea47c38 and ae3c233.

📒 Files selected for processing (8)
  • router-tests/authentication_test.go (3 hunks)
  • router-tests/utils.go (1 hunks)
  • router/core/supervisor_instance.go (1 hunks)
  • router/pkg/authentication/jwks_token_decoder.go (1 hunks)
  • router/pkg/config/config.go (1 hunks)
  • router/pkg/config/config.schema.json (2 hunks)
  • router/pkg/config/config_test.go (1 hunks)
  • router/pkg/config/testdata/config_full.json (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
router/pkg/config/config.schema.json (2)

Learnt from: SkArchon
PR: #1929
File: router/internal/circuit/manager.go:16-25
Timestamp: 2025-06-30T20:39:02.387Z
Learning: In the Cosmo router project, parameter validation for circuit breaker configuration is handled at the JSON schema level rather than through runtime validation methods on structs. The config.schema.json file contains comprehensive validation constraints for circuit breaker parameters.

Learnt from: endigma
PR: #2009
File: router/pkg/config/config.go:0-0
Timestamp: 2025-07-03T10:33:25.778Z
Learning: The CardinalityLimit field in the Metrics struct (router/pkg/config/config.go) is validated at the JSON schema level in config.schema.json with a minimum value constraint of 1, preventing zero or negative values without requiring runtime validation.

⏰ 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). (12)
  • GitHub Check: build-router
  • GitHub Check: image_scan (nonroot)
  • GitHub Check: build_push_image
  • GitHub Check: build_push_image (nonroot)
  • GitHub Check: integration_test (./. ./fuzzquery ./lifecycle ./modules)
  • GitHub Check: integration_test (./telemetry)
  • GitHub Check: image_scan
  • GitHub Check: build_test
  • GitHub Check: integration_test (./events)
  • GitHub Check: build_test
  • GitHub Check: Analyze (go)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (5)
router/core/supervisor_instance.go (1)

258-262: Mutual Exclusivity Already Enforced in JSON Schema

I’ve verified that in router/pkg/config/config.schema.json the JWKS config uses a oneOf with a complementary not + anyOf rule to forbid secret, algorithm or key_id when url is present:

"oneOf": [
  {
    "required": ["url"],
    "not": {
      "anyOf": [
        { "required": ["secret"] },
        { "required": ["algorithm"] },
        { "required": ["key_id"] }
      ]
    }
  },
  {
    "required": ["secret","algorithm","key_id"],
    "additionalProperties": false,
    "properties": { }
  }
]

This ensures URL-based and secret-based JWKS configurations remain mutually exclusive. No further changes are required.

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

458-464: LGTM! Clean struct extension with clear purpose.

The addition of the three new fields to support secret-based JWKS configuration is well-implemented with appropriate types, YAML tags, and helpful comments explaining their purpose.

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

469-473: LGTM! Test data properly updated for new fields.

The addition of the three new fields (Secret, Algorithm, KeyId) to all JWKS entries with empty string values correctly demonstrates the extended configuration structure while maintaining valid JSON syntax.

Also applies to: 480-484, 488-492

router-tests/utils.go (1)

45-70: LGTM! Good refactoring to support flexible JWKS testing.

The refactoring separates concerns well by extracting ConfigureAuthWithJwksConfig to handle arbitrary JWKS configurations while preserving the original ConfigureAuth behavior. This enables testing of both URL-based and secret-based JWKS scenarios effectively.

The changes maintain:

  • Original function signatures and behavior
  • Proper error handling
  • Test cleanup through the context
router-tests/authentication_test.go (1)

808-924: Excellent test coverage for secret-based JWKS authentication!

The test comprehensively covers:

  • Basic HS256 token authentication
  • Mixed HTTP and secret-based JWKS configurations
  • Key ID mismatch scenarios

Comment thread router/pkg/authentication/jwks_token_decoder.go
Comment thread router/pkg/config/config.schema.json
@coderabbitai coderabbitai Bot mentioned this pull request Jul 21, 2025
5 tasks
@StarpTech

StarpTech commented Jul 21, 2025

Copy link
Copy Markdown
Contributor

@SkArchon please update the title. This would not describe the feature accurately.

Comment thread router-tests/authentication_test.go Outdated
@SkArchon SkArchon force-pushed the milinda/eng-7583-support-symmetric-key-algorithm-for-jwk branch from c5c761d to 842f9eb Compare July 21, 2025 17:31
@SkArchon SkArchon changed the title feat: authorization using secrets feat: support symmetric key algorithms for JWKs Jul 21, 2025
Comment thread router-tests/authentication_test.go Outdated
Comment thread router/pkg/config/config.schema.json Outdated
Comment thread router/pkg/config/config.schema.json Outdated
@coderabbitai coderabbitai Bot mentioned this pull request Jul 22, 2025
5 tasks
@SkArchon SkArchon requested a review from Noroth July 22, 2025 13:38
@SkArchon SkArchon dismissed StarpTech’s stale review July 23, 2025 10:15

The review comment of unclear test case name has been done and verified by ludwig (there was a similar comment in ludwig's review)

@SkArchon SkArchon merged commit 9bbdfbb into main Jul 23, 2025
45 of 48 checks passed
@SkArchon SkArchon deleted the milinda/eng-7583-support-symmetric-key-algorithm-for-jwk branch July 23, 2025 10:19
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