Skip to content

Add Redis-based token denylist implementation - #1660

Open
tri-pti wants to merge 1 commit into
imDarshanGK:mainfrom
tri-pti:fix-1634-redis-token-denylist
Open

Add Redis-based token denylist implementation#1660
tri-pti wants to merge 1 commit into
imDarshanGK:mainfrom
tri-pti:fix-1634-redis-token-denylist

Conversation

@tri-pti

@tri-pti tri-pti commented Jul 23, 2026

Copy link
Copy Markdown

Description

TokenDenylist stored revoked JWT jtis in a process-local dict guarded by a threading.Lock. In a multi-replica deployment (the example k8s manifest sets replicas: 2), a token revoked via /logout on one pod was invisible to the others, so it stayed usable on any pod that didn't handle the logout request until it naturally expired.

This PR splits the denylist into two interchangeable implementations behind the same revoke / is_revoked / clear interface:

  • InMemoryTokenDenylist — the original process-local behavior, used when REDIS_URL is unset (e.g. single-replica local dev via docker-compose.yml).
  • RedisTokenDenylist — stores revocations in Redis (SET ... EX / EXISTS), shared by every replica/worker behind the load balancer. Each entry's TTL matches the token's own remaining lifetime, so Redis expires the bookkeeping key for free.

_build_token_denylist() picks between them at import time based on settings.redis_url, and falls back to the in-memory store (with a logged warning) if Redis is configured but unreachable at startup — a Redis outage degrades to single-replica-safe behavior instead of crashing the app. The Redis client is constructed with an explicit socket_connect_timeout/socket_timeout (5s) so that fallback actually happens fast even when the network silently drops packets (common with k8s NetworkPolicies/security group DROP rules) rather than actively refusing the connection.

security.py (get_current_user) and routers/auth.py (logout) are unchanged — both call sites only ever depended on is_revoked/revoke, not the storage mechanism.

Also updated:

  • backend/requirements.txt — added redis>=5.0.0
  • .env.example — documents REDIS_URL
  • docker-compose.yml — added an optional redis service (opt-in via REDIS_URL=redis://redis:6379/0; nothing depends on it, so single-replica local dev is unaffected)
  • deploy/k8s/deployment.example.yaml — wires REDIS_URL from a qyverixai-redis secret, since this manifest is the one that actually runs replicas: 2

Related Issue

Closes #1634

Type of change

  • Bug fix
  • New feature / enhancement
  • Documentation update
  • Test addition
  • Refactor

Labels

  • GSSoC'26
  • bug

Checklist

  • I have read CONTRIBUTING.md
  • My branch is up to date with main
  • I have run pytest -v and all tests pass
  • I have not introduced duplicate issues or features
  • My PR title follows the format: feat/fix/docs/test: short description
  • I have added tests for new features (Level 2 and 3 issues)
  • No hardcoded secrets or API keys in my code
  • This PR is linked to a GSSoC 2026 issue

Screenshots (if frontend change)

N/A — backend-only change.

Test evidence

pytest -v
tests/test_token_denylist.py::test_in_memory_revoke_then_is_revoked PASSED
tests/test_token_denylist.py::test_in_memory_unrevoked_jti_is_not_revoked PASSED
tests/test_token_denylist.py::test_in_memory_expired_entry_is_treated_as_not_revoked PASSED
tests/test_token_denylist.py::test_in_memory_falsy_jti_is_ignored PASSED
tests/test_token_denylist.py::test_in_memory_clear_forgets_all_revocations PASSED
tests/test_token_denylist.py::test_redis_revoke_then_is_revoked PASSED
tests/test_token_denylist.py::test_redis_unrevoked_jti_is_not_revoked PASSED
tests/test_token_denylist.py::test_redis_key_ttl_matches_remaining_token_lifetime PASSED
tests/test_token_denylist.py::test_redis_falsy_jti_is_ignored PASSED
tests/test_token_denylist.py::test_redis_clear_forgets_all_revocations PASSED
tests/test_token_denylist.py::test_redis_denylist_is_shared_across_instances PASSED
tests/test_token_denylist.py::test_build_token_denylist_uses_in_memory_when_redis_url_unset PASSED
tests/test_token_denylist.py::test_build_token_denylist_uses_redis_when_reachable PASSED
tests/test_token_denylist.py::test_build_token_denylist_falls_back_when_redis_unreachable PASSED

@tri-pti
tri-pti requested a review from imDarshanGK as a code owner July 23, 2026 11:33
@imDarshanGK

Copy link
Copy Markdown
Owner

@tri-pti Please add a short video demo showcasing your key features for review.

@github-actions

Copy link
Copy Markdown

👋 This PR has had no activity for 7 days.

Please push updates or comment if you still need more time.

Inactive PRs may be closed automatically after 7 more days.

@github-actions github-actions Bot added the stale label Jul 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Logout doesn't revoke tokens across replicas — token_denylist is process-local but the reference k8s deploy runs replicas: 2

2 participants