Skip to content

fix(auth): Use token in AuthTuple#459

Merged
tisnik merged 1 commit intolightspeed-core:mainfrom
omertuc:authzt
Aug 28, 2025
Merged

fix(auth): Use token in AuthTuple#459
tisnik merged 1 commit intolightspeed-core:mainfrom
omertuc:authzt

Conversation

@omertuc
Copy link
Contributor

@omertuc omertuc commented Aug 27, 2025

While implementing authz I've made the authentication module put the claims in the AuthTuple instead of putting the original token because I assumed the original token was unnecessary and I didn't want to make the authz module re-parse the token to get the claims.

That was a mistake, because we need to pass on the user token to the MCP server for it to validate the user's access. So this commit changes the AuthTuple to contain the original token instead of the claims.

The authz module has been updated to parse the claims from the token instead of receiving them as a JSON string. Somewhat hackily, since we don't want to re-verify the token signature, so we assume that the token has already been verified during authentication and just decode the claims from the middle section of the JWT token.

Description

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 #
  • 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.

Adjusted unit-tests, tested that MCP now works correctly manually

Summary by CodeRabbit

  • New Features
    • Added a JWT-based role resolver that derives user roles from token claims using configurable rules.
  • Behavior Changes
    • Authentication now propagates the raw user token instead of parsed claims, while still validating user identity.
  • Security
    • Introduced safer handling of JWT claims extraction for role evaluation.

While implementing authz I've made the authentication module put the
claims in the `AuthTuple` instead of putting the original token because
I assumed the original token was unnecessary and I didn't want to make
the authz module re-parse the token to get the claims.

That was a mistake, because we need to pass on the user token to the MCP
server for it to validate the user's access. So this commit changes the
`AuthTuple` to contain the original token instead of the claims.

The authz module has been updated to parse the claims from the token
instead of receiving them as a JSON string. Somewhat hackily, since we
don't want to re-verify the token signature, so we assume that the token
has already been verified during authentication and just decode the
claims from the middle section of the JWT token.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 27, 2025

Walkthrough

Changes update auth dependency to return the raw JWT token in AuthTuple, add unsafe claim extraction and a new JWT-based roles resolver, and adjust unit tests to align by constructing/validating token strings directly.

Changes

Cohort / File(s) Summary
Auth token return value
src/auth/jwk_token.py
Return AuthTuple as (user_id, username, raw_token) instead of JSON claims; removed json usage.
Authorization resolvers and JWT claims
src/authorization/resolvers.py
Added unsafe_get_claims(token) to base64/json-decode JWT payload without signature validation; introduced JwtRolesResolver with async resolve_roles, rule evaluation, and claim extraction via unsafe_get_claims; imports updated.
Auth unit tests update
tests/unit/auth/test_jwk_token.py
Tests now compare the raw token string; removed claims decoding helper; updated ensure_test_user_id_and_name signature and usage.
Authorization unit tests tooling
tests/unit/authorization/test_resolvers.py
Added claims_to_token helper to construct token-like strings from claims; updated tests to use it; added json/base64 imports.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor C as Caller
  participant R as JwtRolesResolver
  participant G as _get_claims
  participant U as unsafe_get_claims
  participant E as evaluate_role_rules

  C->>R: resolve_roles(auth: AuthTuple)
  R->>G: _get_claims(auth)
  alt has token
    G->>U: decode payload (base64/json)
    U-->>G: jwt_claims or error
    alt claims ok
      G-->>R: claims dict
      loop for each rule
        R->>E: evaluate_role_rules(rule, claims)
        E-->>R: roles subset
      end
      R-->>C: aggregated roles
    else decoding error/missing claims
      G-->>R: raise RoleResolutionError
      R-->>C: error
    end
  else NO_USER_TOKEN
    G-->>R: {}
    R-->>C: roles based on empty claims
  end

  note over U,R: Decoding is unsafe (no signature validation).
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • tisnik
  • umago

Poem

I hop through claims with careful cheer,
Unwrap the token, payload clear.
Rules like carrots, crisp and fine,
I nibble roles along the line.
A whisker twitch—no sigs today—
Still, all paths lead to roles at play. 🥕🐇

✨ 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.
    • 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.
  • 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 the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

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

CodeRabbit Commands (Invoked using PR/Issue comments)

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

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

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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

@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 (5)
tests/unit/auth/test_jwk_token.py (1)

175-181: Helper reads raw token now—tighten naming for clarity

Rename params/locals to reflect “token” explicitly.

-def ensure_test_user_id_and_name(auth_tuple, expected_token):
+def ensure_test_user_id_and_name(auth_tuple, expected_user_token):
     """Utility to ensure that the values in the auth tuple match the test values."""
-    user_id, username, token = auth_tuple
+    user_id, username, user_token = auth_tuple
     assert user_id == TEST_USER_ID
     assert username == TEST_USER_NAME
-    assert token == expected_token
+    assert user_token == expected_user_token
src/authorization/resolvers.py (2)

5-6: Add binascii import for robust base64 error handling

You’ll need it to catch decoding errors cleanly in unsafe_get_claims.

 import logging
+import binascii
 import base64
 import json

42-51: Harden unsafe_get_claims against malformed tokens; raise domain-specific errors

Current code can throw IndexError/decoder exceptions; surface a clear RoleResolutionError instead and validate 3-part JWT structure.

-def unsafe_get_claims(token: str) -> dict[str, Any]:
-    """Get claims from a token without validating the signature.
-
-    A somewhat hacky way to get JWT claims without verifying the signature.
-    We assume verification has already been done during authentication.
-    """
-    payload = token.split(".")[1]
-    padded = payload + "=" * (-len(payload) % 4)
-    return json.loads(base64.urlsafe_b64decode(padded))
+def unsafe_get_claims(token: str) -> dict[str, Any]:
+    """Get claims from a JWT without validating the signature.
+
+    WARNING: Only call this on tokens that were already authenticated.
+    """
+    try:
+        parts = token.split(".")
+        if len(parts) != 3:
+            raise RoleResolutionError("Invalid JWT format (expected 3 segments).")
+        payload = parts[1]
+        padded = payload + "=" * (-len(payload) % 4)
+        raw = base64.urlsafe_b64decode(padded)
+        claims = json.loads(raw)
+        if not isinstance(claims, dict):
+            raise RoleResolutionError("JWT payload is not a JSON object.")
+        return claims
+    except (binascii.Error, json.JSONDecodeError, ValueError) as e:
+        raise RoleResolutionError("Failed to decode JWT payload.") from e
tests/unit/authorization/test_resolvers.py (2)

49-51: Fix outdated comment: third element is the token, not claims

-        # Mock auth tuple with JWT claims as third element
+        # Mock auth tuple with JWT token as third element
         auth = ("user", "token", claims_to_token(jwt_claims))

73-75: Fix outdated comment: third element is the token, not claims

-        # Mock auth tuple with JWT claims as third element
+        # Mock auth tuple with JWT token as third element
         auth = ("user", "token", claims_to_token(jwt_claims))
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3e2d883 and 17d523a.

📒 Files selected for processing (4)
  • src/auth/jwk_token.py (1 hunks)
  • src/authorization/resolvers.py (3 hunks)
  • tests/unit/auth/test_jwk_token.py (1 hunks)
  • tests/unit/authorization/test_resolvers.py (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/unit/authorization/test_resolvers.py (2)
src/authorization/resolvers.py (1)
  • JwtRolesResolver (53-117)
src/models/config.py (3)
  • config (120-126)
  • JwtRoleRule (231-266)
  • JsonPathOperator (223-228)
⏰ 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). (2)
  • GitHub Check: e2e_tests
  • GitHub Check: build-pr
🔇 Additional comments (3)
src/auth/jwk_token.py (1)

193-193: Returning raw JWT in AuthTuple – verification complete

All downstream consumers were checked and indeed expect the third element as the raw JWT string (never parsed into JSON claims), and there is no accidental leakage or persistence of the token:

  • AuthTuple is defined as (UserID, UserName, Token) and in jwk_token.py we return (user_id, username, user_token).
  • Every tuple‐unpack site either names the third element token or ignores it (e.g. user_id, _, token = auth or user_id, user_name, _ = auth), with no occurrences of unpacking into a variable named “claims.”
  • No direct indexing of the AuthTuple’s third element (auth[2]) was found outside test code.
  • All JSON parsing (json.loads) is confined to explicit claim‐extraction utilities (unsafe_get_claims in authorization/resolvers.py), not to the raw token consumers.
  • Logging statements only include username and user ID; the JWT itself is never logged.
  • There is no code path that writes or persists the token to storage.

With these checks complete, no further action is required.

src/authorization/resolvers.py (1)

91-92: LGTM: using unsafe_get_claims(token) here aligns with the new AuthTuple contract

tests/unit/authorization/test_resolvers.py (1)

10-19: claims_to_token helper LGTM

Encodes as URL-safe Base64 and strips padding—matches decoder logic.

@eranco74
Copy link
Contributor

/lgtm

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.

LGTM

@tisnik tisnik merged commit 691c83e into lightspeed-core:main Aug 28, 2025
19 checks passed
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