-
Notifications
You must be signed in to change notification settings - Fork 344
Labels
enhancementNew feature or requestNew feature or requestsecurityImproves securityImproves securitytriageIssues / Features awaiting triageIssues / Features awaiting triage
Milestone
Description
Feature: Configurable JWT Token Expiration Requirement
🎯 Overview
Summary
Add configurable enforcement of JWT token expiration claims, allowing administrators to choose between strict security (mandatory expiration) or flexibility (optional expiration with warnings).
Problem Statement
Currently, tokens created with --exp 0
have no expiration and remain valid forever. While this is sometimes necessary for long-running automations, it poses a security risk. There's no way to enforce expiration requirements or get warnings about non-expiring tokens.
Solution
Introduce a REQUIRE_TOKEN_EXPIRATION
configuration setting that:
- When
true
: Rejects tokens without expiration (strict mode) - When
false
: Accepts tokens without expiration but logs warnings (flexible mode) - Provides clear audit trail for security compliance
Dependencies
- Requires: [Feature Request]: Epic: Secure JWT Token Catalog with Per-User Expiry and Revocation #87 - JWT Token Catalog with Per-User Expiry and Revocation
- Until automatic token renewal is available via the JWT API, some users may need non-expiring tokens
- Once implemented, administrators can enforce expiration and use the API for token renewal
📊 Architecture
flowchart TD
subgraph "Token Validation Flow"
T[Token] --> CHECK{Has exp claim?}
CHECK -->|Yes| VALIDATE[Validate expiration]
CHECK -->|No| CONFIG{REQUIRE_TOKEN_EXPIRATION?}
CONFIG -->|true| REJECT[❌ HTTP 401]
CONFIG -->|false| WARN[⚠️ Log warning]
VALIDATE -->|Valid| OK[✅ Authorized]
VALIDATE -->|Expired| EXPIRED[❌ Token expired]
WARN --> OK
end
subgraph "Configuration"
ENV[.env] --> SETTING[REQUIRE_TOKEN_EXPIRATION=true/false]
end
style REJECT fill:#FFB6C1
style WARN fill:#FFA500
style OK fill:#90EE90
style EXPIRED fill:#FFB6C1
🏗️ Technical Design
Configuration
# In settings.py
require_token_expiration: bool = Field(
default=False, # Default to flexible mode for backward compatibility
description="Require all JWT tokens to have expiration claims"
)
Environment Variable
# .env
REQUIRE_TOKEN_EXPIRATION=false # Allow non-expiring tokens with warnings
# or
REQUIRE_TOKEN_EXPIRATION=true # Strict mode - reject non-expiring tokens
Implementation
async def verify_jwt_token(token: str) -> dict:
"""Verify and decode a JWT token with configurable expiration requirement."""
try:
# First decode to check claims
unverified = jwt.decode(token, options={"verify_signature": False})
# Check for expiration claim
if "exp" not in unverified and settings.require_token_expiration:
raise jwt.MissingRequiredClaimError("exp")
# Log warning for non-expiring tokens
if "exp" not in unverified:
logger.warning(
"JWT token without expiration accepted. "
"Consider enabling REQUIRE_TOKEN_EXPIRATION for better security. "
f"Token sub: {unverified.get('sub', 'unknown')}"
)
# Full validation
options = {}
if settings.require_token_expiration:
options["require"] = ["exp"]
payload = jwt.decode(
token,
settings.jwt_secret_key,
algorithms=[settings.jwt_algorithm],
options=options
)
return payload
except jwt.MissingRequiredClaimError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token is missing required expiration claim. Set REQUIRE_TOKEN_EXPIRATION=false to allow.",
headers={"WWW-Authenticate": "Bearer"},
)
except jwt.ExpiredSignatureError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token has expired",
headers={"WWW-Authenticate": "Bearer"},
)
CLI Warning Enhancement
# In create_jwt_token.py
def _create_jwt_token(...):
"""Create JWT with optional expiration warning."""
payload = data.copy()
if expires_in_minutes > 0:
expire = _dt.datetime.now(_dt.timezone.utc) + _dt.timedelta(minutes=expires_in_minutes)
payload["exp"] = int(expire.timestamp())
else:
# Warn about non-expiring token
print(
"⚠️ WARNING: Creating token without expiration. This is a security risk!\n"
" Consider using --exp with a value > 0 for production use.\n"
" Once JWT API (#425) is available, use it for automatic token renewal.",
file=sys.stderr
)
return jwt.encode(payload, secret, algorithm=algorithm)
👥 User Story
As a security-conscious API administrator
I want to configure whether tokens must have expiration
So that I can balance security requirements with operational needs
Acceptance Criteria:
- Configuration via
REQUIRE_TOKEN_EXPIRATION
environment variable - When enabled: tokens without
exp
return HTTP 401 - When disabled: tokens without
exp
work but log warnings - CLI warns when creating non-expiring tokens
- Clear documentation on security implications
🛠️ Implementation Plan
Phase 1: Core Configuration
- Add
require_token_expiration
to settings with defaultfalse
- Update
verify_jwt_token()
to check configuration - Implement conditional expiration requirement
- Add warning logs for non-expiring tokens
Phase 2: Enhanced Logging
- Log warnings with token metadata (sub, iat)
- Add metrics/monitoring for non-expiring tokens
- Create audit trail for compliance reporting
Phase 3: CLI Enhancement
- Add warning when using
--exp 0
- Reference JWT API ([SECURITY FEATURE]: Make JWT Token Expiration Mandatory when REQUIRE_TOKEN_EXPIRATION=true (depends on #87) #425) in warning message
- Add
--force
flag to suppress warnings if needed
Phase 4: Documentation
- Document
REQUIRE_TOKEN_EXPIRATION
setting - Explain security trade-offs
- Provide migration guide from non-expiring to expiring tokens
- Reference JWT API ([SECURITY FEATURE]: Make JWT Token Expiration Mandatory when REQUIRE_TOKEN_EXPIRATION=true (depends on #87) #425) for automatic renewal
📋 Test Scenarios
Configuration Testing
# Test 1: Flexible mode (default)
export REQUIRE_TOKEN_EXPIRATION=false
export NO_EXP_TOKEN=$(python3 -m mcpgateway.utils.create_jwt_token --username test --exp 0)
curl -H "Authorization: Bearer $NO_EXP_TOKEN" http://localhost:4444/version
# Returns: 200 OK (with warning in logs)
# Test 2: Strict mode
export REQUIRE_TOKEN_EXPIRATION=true
curl -H "Authorization: Bearer $NO_EXP_TOKEN" http://localhost:4444/version
# Returns: 401 Unauthorized
# {"detail": "Token is missing required expiration claim. Set REQUIRE_TOKEN_EXPIRATION=false to allow."}
# Test 3: Token with expiration works in both modes
export EXP_TOKEN=$(python3 -m mcpgateway.utils.create_jwt_token --username test --exp 60)
curl -H "Authorization: Bearer $EXP_TOKEN" http://localhost:4444/version
# Returns: 200 OK (regardless of REQUIRE_TOKEN_EXPIRATION)
CLI Warning Test
# Creating non-expiring token shows warning
$ python3 -m mcpgateway.utils.create_jwt_token --username admin --exp 0
⚠️ WARNING: Creating token without expiration. This is a security risk!
Consider using --exp with a value > 0 for production use.
Once JWT API (#425) is available, use it for automatic token renewal.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
📚 Documentation Template
## JWT Token Expiration Configuration
### Setting: REQUIRE_TOKEN_EXPIRATION
Controls whether JWT tokens must include an expiration claim.
- `false` (default): Accepts tokens without expiration, logs warnings
- `true`: Rejects tokens without expiration (recommended for production)
### Security Considerations
Non-expiring tokens pose security risks:
- Cannot be invalidated without rotating secrets
- Remain valid if compromised
- Fail security audits
### Migration Path
1. **Current State**: Use non-expiring tokens with `REQUIRE_TOKEN_EXPIRATION=false`
2. **Transition**: Monitor warnings, identify non-expiring token usage
3. **Future State**: Once JWT API (#425) is available:
- Enable `REQUIRE_TOKEN_EXPIRATION=true`
- Use API for automatic token renewal
- Implement token rotation policies
### Examples
# Development - Allow flexibility
REQUIRE_TOKEN_EXPIRATION=false
# Production - Enforce security
REQUIRE_TOKEN_EXPIRATION=true
# Until #425 is implemented, long-lived tokens may be necessary:
python3 -m mcpgateway.utils.create_jwt_token --username automation --exp 43200 # 30 days
🔄 Migration Strategy
Before JWT API (#425)
- Allow non-expiring tokens for critical automations
- Use long expiration (30-90 days) where possible
- Monitor and log all non-expiring token usage
After JWT API (#425)
- Enable
REQUIRE_TOKEN_EXPIRATION=true
- Migrate to shorter expiration (1-7 days)
- Implement automatic token renewal via API
- Phase out all non-expiring tokens
📊 Success Metrics
- % of tokens with expiration (target: increase over time)
- Warning log frequency (target: decrease to zero)
- Security audit compliance score
- Zero service disruptions during migration
🔗 Standards Compliance
- ✅ Configurable to meet various security policies
- ✅ Provides path to JWT best practices
- ✅ Backward compatible by default
- ✅ Clear audit trail for compliance
📝 Notes
- Default
false
maintains backward compatibility - Warning logs help identify technical debt
- Configuration allows gradual security hardening
- JWT API ([SECURITY FEATURE]: Make JWT Token Expiration Mandatory when REQUIRE_TOKEN_EXPIRATION=true (depends on #87) #425) enables full security without operational impact
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or requestsecurityImproves securityImproves securitytriageIssues / Features awaiting triageIssues / Features awaiting triage