Skip to content

Fix JWT Token Creation Divergence#2263

Merged
crivetimihai merged 4 commits intomainfrom
fix-create-jwt
Jan 24, 2026
Merged

Fix JWT Token Creation Divergence#2263
crivetimihai merged 4 commits intomainfrom
fix-create-jwt

Conversation

@madhav165
Copy link
Copy Markdown
Collaborator

@madhav165 madhav165 commented Jan 21, 2026

🐛 Bug-fix PR

Closes #2261


📌 Summary

Unifies JWT token creation between CLI and API paths to produce consistent token formats with rich claims support.

Changes

Core Implementation

  • Enhanced _create_jwt_token() (mcpgateway/utils/create_jwt_token.py)

    • Added optional parameters: user_data, teams, namespaces, scopes
    • Auto-generates namespaces from teams when not provided
    • Maintains backward compatibility with simple tokens
  • Simplified token_catalog_service._generate_token() (mcpgateway/services/token_catalog_service.py)

    • Delegates to enhanced create_jwt_token() with structured data
    • Removes redundant payload building logic
    • Single source of truth for token creation

CLI Enhancement

  • New flags for rich token creation (mcpgateway/utils/create_jwt_token.py)
    • --admin: Mark user as admin
    • --teams: Comma-separated team IDs
    • --scopes: JSON scopes object
    • --full-name: User's full name
    • Displays ⚠️ security warning when using elevated claims

Documentation

  • README.md: Comprehensive security warning section with examples for simple, admin, team-scoped, and permission-scoped tokens
  • docs/docs/manage/api-usage.md: Updated authentication section with security warnings
  • docs/docs/overview/quick_start.md: Added development-only warning
  • docs/docs/manage/securing.md: Added CLI security warnings to token scoping examples
  • docs/docs/index.md: Added inline security comment

Tests

  • 10 new tests for rich token functionality (tests/unit/mcpgateway/utils/test_create_jwt_token.py)
    • User data, teams, namespaces, scopes
    • Auto-generation of namespaces from teams
    • Backward compatibility verification
  • Updated 6 tests in token_catalog_service.py to work with new signature
  • All 22 create_jwt_token tests passing ✅
  • All 68 token_catalog_service tests passing ✅

Example Usage

Before (Simple Token)

export TOKEN=$(python3 -m mcpgateway.utils.create_jwt_token \
  --username admin@example.com --exp 10080 --secret my-test-key)

After (Rich Admin Token)

export TOKEN=$(python3 -m mcpgateway.utils.create_jwt_token \
  --username admin@example.com \
  --admin \
  --teams team-123,team-456 \
  --full-name "Admin User" \
  --exp 10080 \
  --secret my-test-key 2>/dev/null | head -1)

Token now includes:

  • user: {email, full_name, is_admin: true, auth_provider: 'cli'}
  • teams: ['team-123', 'team-456']
  • namespaces: ['user:admin@example.com', 'public', 'team:team-123', 'team:team-456']
  • scopes: {...} (when provided)

Security Impact

No new vulnerabilities - CLI already has JWT_SECRET_KEY and can create any token. This change makes the CLI more capable within its existing trust boundary.

Runtime protection maintained - Token scoping middleware validates team membership at request time regardless of how token was created.

Clear documentation - Security warnings prominently displayed in CLI output and all documentation.

Breaking Changes

None - Simple tokens without rich claims continue to work exactly as before. Backward compatible.

🧪 Verification

Check Command Status
Lint suite make lint pass
Unit tests make test pass

📐 MCP Compliance (if relevant)

  • Matches current MCP spec
  • No breaking change to MCP clients

✅ Checklist

  • Code formatted (make black isort pre-commit)
  • No secrets/credentials committed

@madhav165 madhav165 marked this pull request as draft January 21, 2026 10:22
@crivetimihai crivetimihai added this to the Release 1.0.0-RC1 milestone Jan 21, 2026
@madhav165 madhav165 marked this pull request as ready for review January 21, 2026 11:54
@madhav165 madhav165 marked this pull request as draft January 21, 2026 11:59
@madhav165 madhav165 marked this pull request as ready for review January 21, 2026 12:34
@madhav165 madhav165 force-pushed the fix-create-jwt branch 2 times, most recently from 93582c2 to 8b1c823 Compare January 24, 2026 10:58
@crivetimihai crivetimihai self-assigned this Jan 24, 2026
Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>
- Add ge=1 validation to TokenCreateRequest.expires_in_days schema
- Add guard in _generate_token to reject expires_at in the past
- Use math.ceil() and max(1, ...) to ensure exp is always set for
  sub-minute expirations (prevents rounding to 0)
- Mark --secret and --algo CLI args as deprecated (always uses config)
- Add tests for past expiry rejection and ceiling behavior

This fixes a security regression where negative/zero expires_in_days
could create permanent tokens instead of expired ones.

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
The --secret and --algo CLI parameters now work as optional overrides:
- When provided, they override the configuration values
- When not provided, JWT_SECRET_KEY and JWT_ALGORITHM from config are used

This preserves backward compatibility while still defaulting to
configuration-based signing for consistency.

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Prevent invalid token generation by requiring --secret when --algo
is provided. Using --algo alone would mix config-based keys with a
different algorithm, potentially producing tokens that fail validation.

Also fixes stale docstring that still referenced DEFAULT_SECRET/DEFAULT_ALGO
instead of the new empty-string defaults with config fallback.

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
@crivetimihai
Copy link
Copy Markdown
Member

Changes Made During Review

This PR has been rebased on main and enhanced with the following fixes:

1. Security Fix: Prevent Non-Expiring Tokens from Invalid expires_in_days

Commit: e1e11ff33

  • Added ge=1 validation to expires_in_days in schema (mcpgateway/schemas.py)
  • Added guard in _generate_token to reject already-expired expiration times
  • Uses math.ceil() + max(1, ...) for sub-minute expiry handling
  • Added comprehensive tests for edge cases

2. Restore --secret and --algo CLI Options

Commit: dc0df2783

The original PR inadvertently made --secret and --algo ignored (always using config). This has been fixed:

  • --secret now works as an optional override (uses JWT_SECRET_KEY from config if not provided)
  • --algo now works as an optional override (uses JWT_ALGORITHM from config if not provided)
  • Updated docstrings to reflect the new behavior
  • CLI help text accurately describes the fallback behavior

3. Require --secret When --algo is Specified

Commit: b8e089b34

Prevents invalid token generation by requiring --secret when --algo is provided:

  • Using --algo alone would mix config-based keys with a different algorithm
  • This could produce tokens that fail validation (e.g., RS256 config + HS256 override)
  • Clear error message guides users to either provide both or omit both
  • Fixed stale docstring that still referenced old defaults

Testing Performed

  • All 92 unit tests pass
  • Comprehensive integration testing against docker-compose deployment:
    • CLI token generation with --secret override ✓
    • CLI token generation using JWT_SECRET_KEY from config ✓
    • Rich tokens with --admin, --teams, --scopes
    • API token generation via /tokens endpoint ✓
    • Security validation rejects expires_in_days=0 and -1
    • --secret override actually uses different signing key ✓
    • Tool invocation via RPC ✓

CLI Behavior Summary

# Uses JWT_SECRET_KEY from config
python3 -m mcpgateway.utils.create_jwt_token -u admin@example.com

# Uses explicit --secret override
python3 -m mcpgateway.utils.create_jwt_token -u admin@example.com --secret my-key

# Full override with both --secret and --algo
python3 -m mcpgateway.utils.create_jwt_token -u admin@example.com --secret my-key --algo HS512

# ERROR: --algo without --secret is rejected
python3 -m mcpgateway.utils.create_jwt_token -u admin@example.com --algo HS512

@crivetimihai crivetimihai merged commit 13fc35d into main Jan 24, 2026
53 checks passed
@crivetimihai crivetimihai deleted the fix-create-jwt branch January 24, 2026 21:47
kcostell06 pushed a commit to kcostell06/mcp-context-forge that referenced this pull request Feb 24, 2026
* update jwt cli with more inputs

Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>

* fix: prevent non-expiring tokens from invalid expires_in_days

- Add ge=1 validation to TokenCreateRequest.expires_in_days schema
- Add guard in _generate_token to reject expires_at in the past
- Use math.ceil() and max(1, ...) to ensure exp is always set for
  sub-minute expirations (prevents rounding to 0)
- Mark --secret and --algo CLI args as deprecated (always uses config)
- Add tests for past expiry rejection and ceiling behavior

This fixes a security regression where negative/zero expires_in_days
could create permanent tokens instead of expired ones.

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

* fix: restore --secret and --algo CLI options

The --secret and --algo CLI parameters now work as optional overrides:
- When provided, they override the configuration values
- When not provided, JWT_SECRET_KEY and JWT_ALGORITHM from config are used

This preserves backward compatibility while still defaulting to
configuration-based signing for consistency.

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

* fix: require --secret when --algo is specified

Prevent invalid token generation by requiring --secret when --algo
is provided. Using --algo alone would mix config-based keys with a
different algorithm, potentially producing tokens that fail validation.

Also fixes stale docstring that still referenced DEFAULT_SECRET/DEFAULT_ALGO
instead of the new empty-string defaults with config fallback.

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

---------

Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
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.

[BUG]: JWT token creation divergence between CLI and API

2 participants