Skip to content

Conversation

@omertuc
Copy link
Contributor

@omertuc omertuc commented Jul 15, 2025

Description

Add support for JWT Token authentication with JWK

This commit introduces a new authentication module jwk-token that allows the application to authenticate users using JSON Web Tokens (JWT) signed with JSON Web Keys (JWK). The new module fetches JWKs from a specified URL and validates incoming JWTs against these keys.

Example config:

authentication:
  module: jwk-token
  jwk_config:
    url: https://sso.redhat.com/auth/realms/redhat-external/protocol/openid-connect/certs
    jwt_configuration:
      user_id_claim: user_id
      username_claim: username

Type of change

  • Refactor
  • New feature
  • Bug fix
  • CVE fix
  • Optimization
  • Documentation Update
  • Configuration Update
  • Bump-up service version
  • Bump-up dependent library
  • Bump-up library or tool used for development (does not change the final image)
  • CI configuration change
  • Konflux configuration change
  • Unit tests improvement
  • Integration tests improvement
  • End to end tests improvement

Related Tickets & Documents

  • Related Issue #

MGMT-21125

  • Closes #

Checklist before requesting a review

  • I have performed a self-review of my code.
  • PR has passed all pre-merge test jobs.
  • If it is a core feature, I have added thorough tests.

Testing

  • Please provide detailed steps to perform tests related to this code change.
  • How were the fix/results from this change verified? Please provide relevant screenshots or results.

Just ran it

Summary by CodeRabbit

  • New Features

    • Added support for JWT authentication using JSON Web Keys (JWK), including configuration options for claim names and remote JWK sets.
    • Introduced new authentication settings in the configuration, allowing selection of JWK-based authentication.
  • Bug Fixes

    • Improved validation to ensure required authentication configuration fields are present when using JWK authentication.
  • Tests

    • Added comprehensive unit tests for JWK-based JWT authentication, covering successful authentication, error scenarios, and custom claim handling.
  • Chores

    • Updated dependencies to include required libraries for JWK and JWT authentication.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 15, 2025

"""

Walkthrough

The changes introduce a new authentication module supporting JWT validation using JSON Web Keys (JWK). This includes new configuration models, constants, dependency classes, and comprehensive unit tests. The authentication configuration is extended to support JWK, and the main authentication dependency selector is updated to handle the new module. Required dependencies are added to the project.

Changes

File(s) Change Summary
pyproject.toml Added dependencies: aiohttp (>=3.12.14), authlib (>=1.6.0); added dev dependency: types-cachetools (>=6.1.0.20250717).
src/constants.py Added AUTH_MOD_JWK_TOKEN, DEFAULT_JWT_UID_CLAIM, DEFAULT_JWT_USER_NAME_CLAIM; updated supported authentication modules.
src/models/config.py Added JwtConfiguration, JwkConfiguration; extended AuthenticationConfiguration with jwk_config and related validation.
src/configuration.py Changed authentication_configuration property to always return a value; added assertion for presence of authentication config.
src/auth/init.py Added handling for the new JWK token authentication module in the dependency selector.
src/auth/jwk_token.py New module implementing JwkTokenAuthDependency for JWT validation via JWK, with caching, concurrency control, and error handling.
tests/unit/auth/test_jwk_token.py New unit tests for JWK-based JWT authentication, covering valid, expired, invalid, missing claims, multi-key scenarios, and errors.
tests/unit/models/test_config.py Updated expected config dump to include "jwk_config": None in authentication config assertions.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant FastAPI
    participant JwkTokenAuthDependency
    participant JWK_Server

    Client->>FastAPI: HTTP request with Authorization: Bearer <JWT>
    FastAPI->>JwkTokenAuthDependency: Call dependency
    JwkTokenAuthDependency->>JWK_Server: (If cache miss) Fetch JWK set
    JWK_Server-->>JwkTokenAuthDependency: Return JWK set
    JwkTokenAuthDependency->>JwkTokenAuthDependency: Validate and decode JWT
    JwkTokenAuthDependency-->>FastAPI: Return (user_id, username, token)
    FastAPI-->>Client: Processed response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇
A token hops in, signed with a key,
JWKs fetched swiftly, as quick as can be.
Claims are inspected, the cache keeps them neat,
If all checks out, authentication’s a treat!
With tests all in place, our module’s complete—
Secure as a carrot, and just as sweet!

"""

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link
Contributor

@umago umago left a comment

Choose a reason for hiding this comment

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

Thanks for the patch, comments inline

Copy link
Contributor

@tisnik tisnik left a comment

Choose a reason for hiding this comment

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

in overall it looks ok, thank you.

pls. look at comments made by others

@omertuc omertuc force-pushed the jwk branch 2 times, most recently from d25d34b to ee23335 Compare July 16, 2025 12:48
@omertuc
Copy link
Contributor Author

omertuc commented Jul 16, 2025

Resolved most comments, still didn't write tests and still testing a bit manually

Copy link
Contributor

@umago umago left a comment

Choose a reason for hiding this comment

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

Code-wise LGTM, thank you!

Once tests are added and linters are fixed I think this is good to go!

@omertuc
Copy link
Contributor Author

omertuc commented Jul 17, 2025

Tests are passing in isolation but failing when ran all together, some side effect thing. Need to debug

@omertuc omertuc force-pushed the jwk branch 3 times, most recently from 08d8ac1 to 969c597 Compare July 18, 2025 12:50
@omertuc omertuc changed the title WIP: Add support for JWT Token authentication with JWK Add support for JWT Token authentication with JWK Jul 18, 2025
@omertuc omertuc marked this pull request as ready for review July 18, 2025 13:13
@omertuc
Copy link
Contributor Author

omertuc commented Jul 18, 2025

OK ready for review now

Copy link
Contributor

@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: 5

🧹 Nitpick comments (2)
src/models/config.py (1)

140-143: Remove the empty validator or add validation logic.

The check_jwt_configuration validator doesn't perform any validation. Either remove it entirely or add actual validation logic if needed.

-    @model_validator(mode="after")
-    def check_jwt_configuration(self) -> Self:
-        """Validate JWT configuration."""
-        return self
src/auth/jwk_token.py (1)

27-32: Consider adding cache invalidation mechanism for key rotation scenarios.

The 1-hour TTL cache is reasonable, but consider adding a mechanism to invalidate the cache if authentication failures suggest key rotation. This would help handle scenarios where keys are rotated more frequently.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1c873fb and 969c597.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • pyproject.toml (2 hunks)
  • src/auth/__init__.py (3 hunks)
  • src/auth/jwk_token.py (1 hunks)
  • src/configuration.py (1 hunks)
  • src/constants.py (1 hunks)
  • src/models/config.py (2 hunks)
  • tests/unit/auth/test_jwk_token.py (1 hunks)
  • tests/unit/models/test_config.py (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/configuration.py (1)
src/models/config.py (1)
  • AuthenticationConfiguration (160-195)
src/auth/__init__.py (3)
src/configuration.py (2)
  • configuration (47-52)
  • authentication_configuration (87-97)
src/auth/jwk_token.py (1)
  • JwkTokenAuthDependency (50-149)
src/models/config.py (1)
  • jwk_configuration (188-195)
src/auth/jwk_token.py (3)
src/auth/interface.py (1)
  • AuthInterface (8-13)
src/auth/utils.py (1)
  • extract_user_token (7-26)
src/models/config.py (1)
  • JwkConfiguration (146-157)
🔇 Additional comments (18)
pyproject.toml (2)

56-56: LGTM: Development dependency addition is appropriate.

The types-cachetools dependency provides proper type annotations for caching utilities used in the JWK implementation.


16-17: Dependencies verified: current stable, no known critical vulnerabilities

Both aiohttp>=3.12.14 and authlib>=1.6.0 are already at their latest stable releases and have no outstanding security issues in mainstream PyPI distributions:

  • aiohttp 3.12.14: Latest stable as of June 4, 2025; no active vulnerabilities flagged in public databases. (CVE-2024-52304 applies only to a specific IBM product context, not the general PyPI package.)
  • authlib 1.6.0: Latest stable as of May 22, 2025; no known security issues in this release.

Recommendations:

  • Keep both packages up to date to receive security and bug-fix releases.
  • Monitor official changelogs and vulnerability databases (e.g., CVE Details, Snyk) for future disclosures.
  • If deploying in specialized environments (e.g., IBM watsonx), verify applicability of enterprise-specific advisories.

No changes required.

tests/unit/models/test_config.py (3)

439-439: LGTM: Consistent test updates for new configuration field.

The addition of "jwk_config": None to the expected authentication configuration correctly reflects the new field in the AuthenticationConfiguration model.


523-523: LGTM: Consistent test updates maintained.

The test correctly includes the new jwk_config field in the expected serialization output.


625-625: LGTM: All configuration dump tests updated consistently.

The test updates are consistent across all configuration dump scenarios, ensuring the new JWK configuration field is properly tested.

src/auth/__init__.py (3)

6-6: LGTM: Import addition follows established pattern.

The import of the new jwk_token module is correctly added alongside other authentication modules.


18-18: LGTM: Type annotation reflects configuration guarantee.

The removal of Optional from the return type is consistent with the configuration changes that ensure authentication configuration is always present.


35-39: LGTM: JWK token authentication integration is well-implemented.

The new case for AUTH_MOD_JWK_TOKEN follows the established pattern and correctly passes the JWK configuration to the dependency constructor.

src/constants.py (3)

36-36: LGTM: Authentication module constant follows naming convention.

The AUTH_MOD_JWK_TOKEN constant follows the established naming pattern for authentication modules.


43-43: LGTM: Module properly added to supported authentication modules.

The new authentication module is correctly included in the SUPPORTED_AUTHENTICATION_MODULES frozenset.


47-48: LGTM: JWT claim constants are well-defined.

The default JWT claim constants DEFAULT_JWT_UID_CLAIM and DEFAULT_JWT_USER_NAME_CLAIM use reasonable field names that align with common JWT implementations like Red Hat SSO.

src/configuration.py (2)

87-87: LGTM: Type annotation improvement enhances code safety.

Removing Optional from the return type correctly reflects that authentication configuration is always required and present.


93-95: LGTM: Additional assertion provides runtime validation.

The assertion ensures that the authentication configuration is properly loaded before returning it, providing clear error messaging for configuration issues.

src/models/config.py (1)

167-196: LGTM!

The authentication configuration changes properly integrate JWK support with appropriate validation and access patterns.

src/auth/jwk_token.py (1)

60-149: LGTM!

The authentication flow is well-implemented with comprehensive error handling for various JWT validation scenarios. The HTTP status codes are appropriate for each error type.

tests/unit/auth/test_jwk_token.py (3)

233-238: Fix expected status code.

Based on the implementation in jwk_token.py, when there's no Authorization header, extract_user_token raises HTTPException with status 400. The test expectation is correct.


252-257: Fix expected status code.

Based on the implementation, when the Authorization header doesn't contain a valid Bearer token, extract_user_token raises HTTPException with status 400. The test expectation is correct.


145-386: LGTM!

The test suite provides comprehensive coverage of the JWT authentication functionality, including edge cases and error scenarios. The fixtures and mocking are well-designed.

Comment on lines 35 to 45
async def get_jwk_set(url: str) -> KeySet:
"""Fetch the JWK set from the cache, or fetch it from the URL if not cached."""
async with _jwk_cache_lock:
if url not in _jwk_cache:
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
resp.raise_for_status()
_jwk_cache[url] = JsonWebKey.import_key_set(await resp.json())
return _jwk_cache[url]

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add timeout and better error handling for HTTP requests.

The function should handle network timeouts and provide more specific error messages for different failure scenarios.

 async def get_jwk_set(url: str) -> KeySet:
     """Fetch the JWK set from the cache, or fetch it from the URL if not cached."""
     async with _jwk_cache_lock:
         if url not in _jwk_cache:
-            async with aiohttp.ClientSession() as session:
-                async with session.get(url) as resp:
-                    resp.raise_for_status()
-                    _jwk_cache[url] = JsonWebKey.import_key_set(await resp.json())
+            timeout = aiohttp.ClientTimeout(total=10)
+            async with aiohttp.ClientSession(timeout=timeout) as session:
+                try:
+                    async with session.get(url) as resp:
+                        resp.raise_for_status()
+                        jwk_data = await resp.json()
+                        _jwk_cache[url] = JsonWebKey.import_key_set(jwk_data)
+                except aiohttp.ClientError as e:
+                    logger.error("Failed to fetch JWK set from %s: %s", url, e)
+                    raise
+                except ValueError as e:
+                    logger.error("Invalid JSON response from JWK endpoint: %s", e)
+                    raise
         return _jwk_cache[url]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async def get_jwk_set(url: str) -> KeySet:
"""Fetch the JWK set from the cache, or fetch it from the URL if not cached."""
async with _jwk_cache_lock:
if url not in _jwk_cache:
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
resp.raise_for_status()
_jwk_cache[url] = JsonWebKey.import_key_set(await resp.json())
return _jwk_cache[url]
async def get_jwk_set(url: str) -> KeySet:
"""Fetch the JWK set from the cache, or fetch it from the URL if not cached."""
async with _jwk_cache_lock:
if url not in _jwk_cache:
timeout = aiohttp.ClientTimeout(total=10)
async with aiohttp.ClientSession(timeout=timeout) as session:
try:
async with session.get(url) as resp:
resp.raise_for_status()
jwk_data = await resp.json()
_jwk_cache[url] = JsonWebKey.import_key_set(jwk_data)
except aiohttp.ClientError as e:
logger.error("Failed to fetch JWK set from %s: %s", url, e)
raise
except ValueError as e:
logger.error("Invalid JSON response from JWK endpoint: %s", e)
raise
return _jwk_cache[url]
🤖 Prompt for AI Agents
In src/auth/jwk_token.py around lines 35 to 44, the get_jwk_set function lacks
timeout settings and detailed error handling for the HTTP request. Add a timeout
parameter to the aiohttp.ClientSession or the session.get call to prevent
hanging requests. Wrap the HTTP request in a try-except block to catch
exceptions like asyncio.TimeoutError and aiohttp.ClientError, and raise or log
more specific error messages indicating the nature of the failure. This will
improve reliability and debuggability of the function.

@omertuc
Copy link
Contributor Author

omertuc commented Jul 21, 2025

Will handle coderabbit comments and add a few more unittests

@omertuc
Copy link
Contributor Author

omertuc commented Jul 24, 2025

OK finally have time to get back to this. Still a few unit tests refuse to pass, looking into it

@tisnik
Copy link
Contributor

tisnik commented Jul 24, 2025

OK finally have time to get back to this. Still a few unit tests refuse to pass, looking into it

thank you!

Copy link
Contributor

@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: 0

🧹 Nitpick comments (2)
tests/unit/auth/test_jwk_token.py (2)

318-318: Fix incorrect comment.

The comment incorrectly references "user_id claim" when it should reference "username claim" for this fixture.

-    """Token without a user_id claim."""
+    """Token without a username claim."""

419-419: Fix incorrect comment.

The comment incorrectly references "RS256" when it should reference "RS384" for this fixture.

-    """RS256 no kid."""
+    """RS384 no kid."""
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 969c597 and 84f84c1.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • pyproject.toml (2 hunks)
  • src/auth/__init__.py (3 hunks)
  • src/auth/jwk_token.py (1 hunks)
  • src/configuration.py (1 hunks)
  • src/constants.py (1 hunks)
  • src/models/config.py (2 hunks)
  • tests/unit/auth/test_jwk_token.py (1 hunks)
  • tests/unit/models/test_config.py (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
  • pyproject.toml
  • src/configuration.py
  • src/auth/init.py
  • tests/unit/models/test_config.py
  • src/auth/jwk_token.py
  • src/constants.py
  • src/models/config.py
⏰ 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). (1)
  • GitHub Check: build-pr

Copy link
Contributor

@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

♻️ Duplicate comments (2)
src/auth/jwk_token.py (2)

35-44: Add timeout and better error handling for HTTP requests.

This function needs timeout configuration and more specific error handling for different failure scenarios to prevent hanging requests and provide better debugging information.


91-92: Add error handling for missing 'alg' field in JWK.

The code assumes the key has an 'alg' field, which could raise a KeyError if missing from the JWK.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 84f84c1 and 1bb8b5a.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • pyproject.toml (2 hunks)
  • src/auth/__init__.py (3 hunks)
  • src/auth/jwk_token.py (1 hunks)
  • src/configuration.py (1 hunks)
  • src/constants.py (1 hunks)
  • src/models/config.py (2 hunks)
  • tests/unit/auth/test_jwk_token.py (1 hunks)
  • tests/unit/models/test_config.py (3 hunks)
✅ Files skipped from review due to trivial changes (2)
  • tests/unit/models/test_config.py
  • src/constants.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • pyproject.toml
  • src/configuration.py
  • src/auth/init.py
  • src/models/config.py
🔇 Additional comments (12)
src/auth/jwk_token.py (6)

46-48: LGTM!

Clean custom exception class with appropriate docstring and inheritance.


53-58: LGTM!

Constructor correctly initializes the configuration and virtual path with appropriate defaults.


60-64: LGTM!

Proper token extraction and JWK set retrieval following the established patterns.


96-123: LGTM!

Comprehensive error handling with appropriate HTTP status codes and informative error messages for different JWT decode failure scenarios.


124-140: LGTM!

Proper token validation with appropriate error handling for expired tokens and other validation failures.


141-160: LGTM!

Proper claim extraction with configurable claim names, appropriate error handling for missing claims, and good logging of successful authentications.

tests/unit/auth/test_jwk_token.py (6)

3-3: LGTM!

Module docstring correctly references the jwk_token module being tested.


19-127: LGTM!

Comprehensive and well-organized test fixtures providing proper test isolation, key generation, token creation, and mock server setup for different test scenarios.


130-180: LGTM!

Well-designed helper functions that promote code reuse and provide clean abstractions for creating test requests and verifying results.


182-284: LGTM!

Comprehensive basic authentication tests covering valid tokens, expired tokens, invalid signatures, and missing/malformed authorization headers with appropriate status code assertions.


285-391: LGTM!

Thorough testing of claim extraction including missing claims, custom claim names, and proper error handling with appropriate status codes and error messages.


393-538: LGTM!

Excellent multi-key testing including DoS prevention for tokens without kid headers, proper algorithm support, and comprehensive error handling for multi-key scenarios.

@omertuc omertuc force-pushed the jwk branch 2 times, most recently from 345a87a to e4caac9 Compare July 24, 2025 12:34
Copy link
Contributor

@tisnik tisnik left a comment

Choose a reason for hiding this comment

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

looks ok on my side

Copy link
Contributor

@umago umago left a comment

Choose a reason for hiding this comment

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

Overall solid patch. Two nits inline regarding validation of the config models but, it's no harm. Can be fixed in case you need to change something else in the patch

@omertuc
Copy link
Contributor Author

omertuc commented Jul 25, 2025

Improved key selection algorithm and fixed review comments

@omertuc omertuc force-pushed the jwk branch 3 times, most recently from c733537 to 0d8552d Compare July 25, 2025 09:57
Copy link
Contributor

@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: 0

♻️ Duplicate comments (1)
src/auth/jwk_token.py (1)

35-44: Address the TODO comment for error handling and timeouts.

The TODO comment indicates that connection errors and timeouts need to be handled. This is critical for production reliability.

As mentioned in previous reviews, consider implementing:

 async def get_jwk_set(url: str) -> KeySet:
     """Fetch the JWK set from the cache, or fetch it from the URL if not cached."""
     async with _jwk_cache_lock:
         if url not in _jwk_cache:
-            async with aiohttp.ClientSession() as session:
-                # TODO(omertuc): handle connection errors, timeouts, etc.
-                async with session.get(url) as resp:
-                    resp.raise_for_status()
-                    _jwk_cache[url] = JsonWebKey.import_key_set(await resp.json())
+            timeout = aiohttp.ClientTimeout(total=10)
+            try:
+                async with aiohttp.ClientSession(timeout=timeout) as session:
+                    async with session.get(url) as resp:
+                        resp.raise_for_status()
+                        jwk_data = await resp.json()
+                        _jwk_cache[url] = JsonWebKey.import_key_set(jwk_data)
+            except (aiohttp.ClientError, asyncio.TimeoutError) as e:
+                logger.error("Failed to fetch JWK set from %s: %s", url, e)
+                raise HTTPException(
+                    status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
+                    detail="Unable to fetch JWK set"
+                ) from e
         return _jwk_cache[url]
🧹 Nitpick comments (1)
src/auth/jwk_token.py (1)

27-32: Consider increasing cache size for better scalability.

The TTL cache setup is solid with appropriate 1-hour expiration. However, maxsize=3 might be limiting if the application needs to support multiple JWK endpoints or environments.

Consider increasing the cache size to handle more diverse deployment scenarios:

-_jwk_cache: TTLCache[str, KeySet] = TTLCache(maxsize=3, ttl=3600)
+_jwk_cache: TTLCache[str, KeySet] = TTLCache(maxsize=10, ttl=3600)
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between e4caac9 and 0d8552d.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • pyproject.toml (2 hunks)
  • src/auth/__init__.py (3 hunks)
  • src/auth/jwk_token.py (1 hunks)
  • src/configuration.py (1 hunks)
  • src/constants.py (1 hunks)
  • src/models/config.py (2 hunks)
  • tests/unit/auth/test_jwk_token.py (1 hunks)
  • tests/unit/models/test_config.py (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • tests/unit/models/test_config.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/constants.py
  • src/auth/init.py
  • pyproject.toml
  • src/configuration.py
  • src/models/config.py
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/auth/jwk_token.py (3)
src/auth/interface.py (1)
  • AuthInterface (8-13)
src/auth/utils.py (1)
  • extract_user_token (7-26)
src/models/config.py (1)
  • JwkConfiguration (157-161)
⏰ 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). (1)
  • GitHub Check: build-pr
🔇 Additional comments (11)
src/auth/jwk_token.py (4)

1-24: LGTM! Module docstring and imports are correct.

The docstring accurately describes the JWT JWK authentication functionality, and all necessary imports are present for the implementation.


47-48: LGTM! Exception is properly defined.

The custom exception is appropriate for handling key resolution failures in the JWK context.


51-109: Excellent key resolution implementation with comprehensive error handling.

The key resolver function properly addresses previous review concerns:

  • ✅ Validates presence of 'alg' field in token header
  • ✅ Handles kid-based key lookup with algorithm validation
  • ✅ Provides fallback for tokens without kid
  • ✅ Includes DoS protection by limiting key attempts
  • ✅ Provides descriptive error messages for debugging

The logic correctly handles edge cases and provides robust key resolution.


112-192: Robust JWT authentication implementation with comprehensive error handling.

The authentication dependency correctly implements the AuthInterface and provides thorough error handling:

  • ✅ Proper HTTP status codes for different error scenarios
  • ✅ Comprehensive exception handling for JWT decode and validation errors
  • ✅ Configurable claim extraction for user ID and username
  • ✅ Appropriate logging for successful authentication
  • ✅ Returns expected tuple format (user_id, username, token)

The implementation addresses previous review concerns about error handling and HTTP status codes.

tests/unit/auth/test_jwk_token.py (7)

1-17: LGTM! Module docstring and imports are correct.

The docstring correctly references the module being tested, and all necessary imports are present for comprehensive JWT testing.


15-73: Well-structured test setup with proper isolation.

The test fixtures and utilities are well-designed:

  • ✅ Clear test constants for consistent data
  • ✅ Reusable fixtures for tokens and keys
  • ✅ Proper RSA key generation for testing
  • ✅ Cache clearing fixture ensures test isolation
  • ✅ Good separation of concerns in fixture design

The autouse=True on the cache clearing fixture is essential for preventing test interference.


75-151: Comprehensive mock server setup for JWK endpoint simulation.

The mock infrastructure properly handles the complexity of testing async HTTP interactions:

  • ✅ Correctly mocks aiohttp ClientSession and response context managers
  • ✅ Properly converts private keys to public JWK format for server responses
  • ✅ Flexible server configuration for different key sets and algorithms
  • ✅ Helper utilities for request generation and header manipulation

This enables thorough testing of the JWK fetching and caching behavior.


182-283: Comprehensive coverage of basic authentication scenarios.

The test cases thoroughly validate core authentication functionality:

  • ✅ Valid token authentication with correct claim extraction
  • ✅ Expired token handling with appropriate HTTP 401 response
  • ✅ Invalid signature detection and rejection
  • ✅ Missing Authorization header validation (HTTP 400)
  • ✅ Malformed Authorization header validation (HTTP 400)
  • ✅ Proper error message verification for debugging

Each test appropriately verifies both status codes and error messages.


285-345: Proper validation of required JWT claims.

The claim validation tests ensure robust handling of missing required claims:

  • ✅ Missing user_id claim properly rejected with HTTP 401
  • ✅ Missing username claim properly rejected with HTTP 401
  • ✅ Error messages include specific claim names for debugging
  • ✅ Consistent error handling pattern across claim types

This validates the claim extraction and validation logic.


347-391: Excellent validation of configurable claim names.

The custom claims test ensures flexibility in JWT claim configuration:

  • ✅ Supports non-standard claim names for user ID and username
  • ✅ Proper configuration override mechanism
  • ✅ Validates successful extraction of custom claims
  • ✅ Maintains consistent authentication flow with custom configuration

This flexibility is crucial for integrating with diverse identity providers.


393-541: Comprehensive multi-key testing with important security validations.

The multi-key tests validate critical security and functionality aspects:

  • ✅ Multiple keys with different algorithms (RS256, RS384)
  • ✅ Proper kid-based key selection for all keys
  • ✅ DoS protection: tokens without kid only try first matching algorithm key
  • ✅ Fallback behavior when only one key exists for an algorithm
  • ✅ Proper rejection of tokens that can't be verified with first key

The test design correctly validates the security consideration of preventing brute-force key attempts while maintaining functionality for legitimate use cases.

This commit introduces a new authentication module `jwk-token` that
allows the application to authenticate users using JSON Web Tokens (JWT)
signed with JSON Web Keys (JWK). The new module fetches JWKs from a
specified URL and validates incoming JWTs against these keys.

Example config:

```yaml
authentication:
  module: jwk-token
  jwk_config:
    url: https://sso.redhat.com/auth/realms/redhat-external/protocol/openid-connect/certs
    jwt_configuration:
      user_id_claim: user_id
      username_claim: username
```
@omertuc
Copy link
Contributor Author

omertuc commented Jul 25, 2025

Force push for rebase conflicts

Copy link
Contributor

@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: 0

♻️ Duplicate comments (3)
src/auth/jwk_token.py (3)

35-44: Add timeout and comprehensive error handling for HTTP requests.

The function lacks timeout configuration and specific error handling for network failures, which was previously flagged. The TODO comment acknowledges this limitation.


88-94: Add error handling for missing 'alg' field in JWK keys.

The code still assumes JWK keys have an 'alg' field (line 90), which could raise a KeyError if missing. This was flagged in previous reviews but the issue persists.

         key = keys[0]

-        if key["alg"] != header["alg"]:
+        if "alg" not in key:
+            raise KeyNotFoundError(
+                f"Key with kid '{header.get('kid')}' missing 'alg' field"
+            )
+        
+        if key["alg"] != header["alg"]:
             raise KeyNotFoundError(
                 "Key found by kid does not match the algorithm in the token header"
             )

97-107: Add error handling for missing 'alg' field in algorithm-only key selection.

Similar to the kid-based selection, this code assumes keys have an 'alg' field (line 98), which could raise a KeyError.

     # No kid in the token header, we will try to find a key by alg
-    keys = [key for key in jwk_set.keys if key["alg"] == header["alg"]]
+    keys = [key for key in jwk_set.keys if key.get("alg") == header["alg"]]
🧹 Nitpick comments (1)
src/auth/jwk_token.py (1)

25-32: Consider increasing the JWK cache size.

The cache maxsize of 3 may be too restrictive if the application needs to authenticate against multiple JWK endpoints. Consider increasing this value based on expected usage patterns.

The design choice to use a simple Lock instead of RWLock is reasonable to avoid additional dependencies.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0d8552d and 31cca10.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • pyproject.toml (2 hunks)
  • src/auth/__init__.py (3 hunks)
  • src/auth/jwk_token.py (1 hunks)
  • src/configuration.py (1 hunks)
  • src/constants.py (1 hunks)
  • src/models/config.py (2 hunks)
  • tests/unit/auth/test_jwk_token.py (1 hunks)
  • tests/unit/models/test_config.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
  • pyproject.toml
  • tests/unit/models/test_config.py
  • src/configuration.py
  • src/auth/init.py
  • src/constants.py
  • src/models/config.py
  • tests/unit/auth/test_jwk_token.py
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/auth/jwk_token.py (3)
src/auth/interface.py (1)
  • AuthInterface (8-13)
src/auth/utils.py (1)
  • extract_user_token (7-26)
src/models/config.py (1)
  • JwkConfiguration (157-161)
⏰ 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). (1)
  • GitHub Check: build-pr
🔇 Additional comments (3)
src/auth/jwk_token.py (3)

1-24: LGTM: Module setup and imports are well-structured.

The corrected module docstring accurately describes the JWT JWK authentication functionality, and all imports appear necessary and properly organized.


47-48: LGTM: Clear and purposeful custom exception.

The KeyNotFoundError exception is well-defined with a clear docstring explaining its specific use case.


112-191: LGTM: Comprehensive JWT authentication implementation.

The JwkTokenAuthDependency class properly implements the AuthInterface with robust error handling:

  • Appropriate HTTP status codes for different error scenarios
  • Comprehensive exception handling for JWT decoding and validation
  • Clear error messages for missing claims
  • Proper logging of successful authentication
  • Correct return type matching the interface

The implementation follows security best practices and handles edge cases appropriately.

@tisnik tisnik merged commit c7b1cf0 into lightspeed-core:main Jul 25, 2025
17 checks passed
omertuc added a commit to omertuc/assisted-chat that referenced this pull request Jul 25, 2025
Update lightspeed-stack to the latest version which includes this PR:

lightspeed-core/lightspeed-stack#243

Modify our configuration to use the new jwk-token authentication module,
with the JWK URL pointing to the Red Hat SSO server and using the user
ID / username fields that can be found in a typical JWT issued by Red
Hat SSO.
omertuc added a commit to omertuc/assisted-chat that referenced this pull request Jul 28, 2025
Update lightspeed-stack to the latest version which includes this PR:

lightspeed-core/lightspeed-stack#243

Modify our configuration to use the new jwk-token authentication module,
with the JWK URL pointing to the Red Hat SSO server and using the user
ID / username fields that can be found in a typical JWT issued by Red
Hat SSO.
omertuc added a commit to omertuc/assisted-chat that referenced this pull request Jul 28, 2025
Update lightspeed-stack to the latest version which includes this PR:

lightspeed-core/lightspeed-stack#243

Modify our configuration to use the new jwk-token authentication module,
with the JWK URL pointing to the Red Hat SSO server and using the user
ID / username fields that can be found in a typical JWT issued by Red
Hat SSO.
@coderabbitai coderabbitai bot mentioned this pull request Aug 19, 2025
18 tasks
@coderabbitai coderabbitai bot mentioned this pull request Aug 27, 2025
18 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants