fix(auth): Use token in AuthTuple#459
Conversation
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.
WalkthroughChanges 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
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).
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
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 clarityRename 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_tokensrc/authorization/resolvers.py (2)
5-6: Add binascii import for robust base64 error handlingYou’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 errorsCurrent 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 etests/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.
📒 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 completeAll 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 injwk_token.pywe return(user_id, username, user_token).- Every tuple‐unpack site either names the third element
tokenor ignores it (e.g.user_id, _, token = authoruser_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_claimsinauthorization/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 contracttests/unit/authorization/test_resolvers.py (1)
10-19: claims_to_token helper LGTMEncodes as URL-safe Base64 and strips padding—matches decoder logic.
|
/lgtm |
While implementing authz I've made the authentication module put the claims in the
AuthTupleinstead 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
AuthTupleto 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
Related Tickets & Documents
Checklist before requesting a review
Testing
Adjusted unit-tests, tested that MCP now works correctly manually
Summary by CodeRabbit