feat(security): implement JWT token security improvements#4371
Merged
feat(security): implement JWT token security improvements#4371
Conversation
6fef953 to
dfbb7ea
Compare
6df055b to
039faf4
Compare
jonpspri
added a commit
that referenced
this pull request
Apr 26, 2026
The idle-timeout block in get_current_user only read last_activity from the JWT, but no issuance code wrote it — so the check ran zero times on real tokens. Read activity from Redis first (update_activity was already writing there but had no consumer), fall back to the JWT last_activity claim, fall back to iat. Emit last_activity=iat in create_access_token for first-request bootstrap. Folds in remaining PR-review blockers: - bb43712cae28 alembic merge resolves dual-head from rebase past the head referenced by cae28b15a507 - TOKEN_EXPIRY 10080->20 min default documented in CHANGELOG Behavior Changes with migration & rollback guidance - drop dead refresh_token_expiry config field + 3 doc references - /auth/logout current_user: dict -> EmailUser (matches actual return type of get_current_user) - /admin/logout test rewritten with TestClient + CSRF deny-path regression (was asyncio.run on MagicMock — bypassed middleware and would pass even if the route was unregistered) Coverage on diff: 91% -> ~100%. New unit tests cover every branch of the idle-timeout block plus the Redis-success and fresh-session paths in TokenBlocklistService that were previously unreached. Refs #4317, #4371 Signed-off-by: Jonathan Springer <jps@s390x.com>
b8f24e0 to
e579383
Compare
jonpspri
added a commit
that referenced
this pull request
Apr 26, 2026
The idle-timeout block in get_current_user only read last_activity from the JWT, but no issuance code wrote it — so the check ran zero times on real tokens. Read activity from Redis first (update_activity was already writing there but had no consumer), fall back to the JWT last_activity claim, fall back to iat. Emit last_activity=iat in create_access_token for first-request bootstrap. Folds in remaining PR-review blockers: - bb43712cae28 alembic merge resolves dual-head from rebase past the head referenced by cae28b15a507 - TOKEN_EXPIRY 10080->20 min default documented in CHANGELOG Behavior Changes with migration & rollback guidance - drop dead refresh_token_expiry config field + 3 doc references - /auth/logout current_user: dict -> EmailUser (matches actual return type of get_current_user) - /admin/logout test rewritten with TestClient + CSRF deny-path regression (was asyncio.run on MagicMock — bypassed middleware and would pass even if the route was unregistered) Coverage on diff: 91% -> ~100%. New unit tests cover every branch of the idle-timeout block plus the Redis-success and fresh-session paths in TokenBlocklistService that were previously unreached. Refs #4317, #4371 Signed-off-by: Jonathan Springer <jps@s390x.com>
e579383 to
84c9864
Compare
Implements comprehensive JWT token security enhancements addressing X-Force Red security audit findings: Security Features: - Reduced token lifetime from ~70 days to 20 minutes (configurable 5-1440 min) - Server-side token blocklist with Redis caching and database persistence - Idle timeout enforcement (60 minutes default, configurable) - Logout endpoint with immediate token invalidation - Activity tracking with automatic updates - Token revocation on logout, expiry, and idle timeout - Comprehensive audit logging for security events Implementation: - Added TokenBlocklistService for token revocation management - Enhanced TokenRevocation model with token_expiry and last_activity fields - Added idle timeout checking in get_current_user() - Implemented /auth/logout endpoint with proper dependency injection - Added security configuration with validation (TOKEN_LIFETIME_MINUTES, TOKEN_IDLE_TIMEOUT_MINUTES) - Created Alembic migrations for database schema changes Testing: - 47 tests passing (39 unit + 8 edge cases + 11 integration) - 88% coverage on token_blocklist_service.py - 84% coverage on routers/auth.py - 86% overall coverage for JWT security modules - Comprehensive integration tests for all security flows - Edge case tests for error handling paths Documentation: - Added JWT_TOKEN_SECURITY_IMPLEMENTATION.md with complete implementation guide - Added test_jwt_token_security.md with test documentation - Updated .secrets.baseline for security scanning Closes #4317 Signed-off-by: Bogdan-Marius-Catanus <bogdan-marius.catanus@ibm.com> Signed-off-by: Jonathan Springer <jps@s390x.com>
…kens Signed-off-by: Jonathan Springer <jps@s390x.com>
- Remove duplicate datetime/timezone imports in admin.py - Replace __enter__() calls with proper context managers in token_blocklist_service.py - Add pylint disable comments for func.count false positives - Fix dict comprehension to use dict() constructor - Add newline at end of test_jwt_token_security.md Signed-off-by: Jonathan Springer <jps@s390x.com>
- Add test for routers/auth.py line 239 (SecretStr.get_secret_value) - Fix 3 pylint R1705 (no-else-return) violations in token_blocklist_service.py - Update secrets baseline after merge - Add SimpleNamespace import to test_auth.py Improves diff-cover from 90% to 97.4% for routers/auth.py Signed-off-by: Bogdan-Marius-Catanus <bogdan-marius.catanus@ibm.com> Signed-off-by: Jonathan Springer <jps@s390x.com>
The idle-timeout block in get_current_user only read last_activity from the JWT, but no issuance code wrote it — so the check ran zero times on real tokens. Read activity from Redis first (update_activity was already writing there but had no consumer), fall back to the JWT last_activity claim, fall back to iat. Emit last_activity=iat in create_access_token for first-request bootstrap. Folds in remaining PR-review blockers: - bb43712cae28 alembic merge resolves dual-head from rebase past the head referenced by cae28b15a507 - TOKEN_EXPIRY 10080->20 min default documented in CHANGELOG Behavior Changes with migration & rollback guidance - drop dead refresh_token_expiry config field + 3 doc references - /auth/logout current_user: dict -> EmailUser (matches actual return type of get_current_user) - /admin/logout test rewritten with TestClient + CSRF deny-path regression (was asyncio.run on MagicMock — bypassed middleware and would pass even if the route was unregistered) Coverage on diff: 91% -> ~100%. New unit tests cover every branch of the idle-timeout block plus the Redis-success and fresh-session paths in TokenBlocklistService that were previously unreached. Refs #4317, #4371 Signed-off-by: Jonathan Springer <jps@s390x.com>
84c9864 to
86d04d2
Compare
22 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🔗 Related Issue
Closes #4317
Jira Issue: https://jsw.ibm.com/browse/ICACF-18
📝 Summary
This PR implements comprehensive JWT token security improvements to address X-Force Red security audit findings regarding session token management. The implementation reduces token lifetime from ~70 days to 20 minutes (configurable), adds server-side token blocklist functionality, implements idle timeout enforcement, and provides immediate token invalidation on logout.
Key Security Enhancements:
/auth/logoutand enhanced/admin/logoutwith immediate token invalidationTechnical Implementation:
TokenBlocklistServicefor centralized token revocation managementTokenRevocationmodel withtoken_expiryandlast_activityfieldsget_current_user()authentication flow/admin/logoutto revoke tokens in blocklist🏷️ Type of Change
🧪 Verification
make lintmake testmake coverageTest Files:
tests/unit/mcpgateway/test_token_blocklist_service.py- 27 unit tests for blocklist servicetests/unit/mcpgateway/test_admin_logout_token_revocation.py- 5 tests for admin logout token revocationtests/unit/mcpgateway/test_auth_logout.py- 8 tests for logout endpointtests/integration/test_token_security_integration.py- 14 integration tests covering full auth flows including idle✅ Checklist
make black isort pre-commit)docs/security/JWT_TOKEN_SECURITY_IMPLEMENTATION.md)📓 Notes
Files Changed
New Files (10):
mcpgateway/services/token_blocklist_service.py(449 lines) - Core token revocation servicemcpgateway/alembic/versions/aa1_add_token_revocation_idle_timeout_fields.py- Schema migrationmcpgateway/alembic/versions/cae28b15a507_merge_token_revocation_and_uaid_heads.py- Merge migrationdocs/security/JWT_TOKEN_SECURITY_IMPLEMENTATION.md(400+ lines) - Complete implementation guidetests/unit/mcpgateway/test_auth_logout.py(279 lines) - Logout endpoint teststests/unit/mcpgateway/test_admin_logout_token_revocation.py(166 lines) - Admin logout token revocation teststests/unit/mcpgateway/test_token_blocklist_service.py(449 lines) - Blocklist service teststests/integration/test_token_security_integration.py(485 lines) - Integration tests with idle timeout coveragetests/security/test_jwt_token_security.md- Test documentation.secrets.baseline- Updated for new test tokensModified Files (6):
mcpgateway/config.py- Addedtoken_expiry,refresh_token_expiry,token_idle_timeoutwith validationmcpgateway/db.py- EnhancedTokenRevocationmodel withtoken_expiryandlast_activityfieldsmcpgateway/auth.py- Added idle timeout checking and activity tracking inget_current_user()mcpgateway/admin.py- Enhanced/admin/logoutto revoke tokens in blocklistmcpgateway/routers/auth.py- Implemented/auth/logoutendpointmcpgateway/routers/email_auth.py- Enhanced token generation with JTI and activity trackingConfiguration
New environment variables (with secure defaults):
Security Compliance
Addresses all X-Force Red recommendations:
/auth/logoutand/admin/logout)Performance Considerations
Breaking Changes
None. All changes are backward compatible with existing authentication flows.