Backend Authentication Hardening Plan (Redis Architecture)
This plan addresses the critical security and performance issues identified in the authentication flow by introducing Redis as a high-performance, in-memory datastore for session management, token revocation, and caching.
Why Use Redis? (Tradeoffs)
The Good (Why it's beneficial)
- True Token Revocation: By storing a blacklist of revoked Access Tokens in Redis with a Time-To-Live (TTL) matching the token's expiration, we can immediately invalidate stolen or logged-out access tokens.
- Immediate Ban Enforcement without DB Overhead: We can cache banned users in Redis (e.g., a
ban:{username} key). The BannedUserFilter can check this key in sub-millisecond time, enforcing bans immediately without slamming PostgreSQL on every API request.
- Natural Expiration for Sessions: Storing Refresh Tokens in Redis instead of PostgreSQL allows us to use native Redis TTLs. Expired sessions are automatically cleaned up, eliminating database bloat and the need for scheduled cleanup jobs.
- Performance: Redis easily handles thousands of reads per second. Moving the session and ban checks out of Postgres significantly reduces database load and improves overall API latency.
The Bad (Why it's a tradeoff)
- Infrastructure Complexity: Redis becomes a hard dependency. You must run a Redis container in all environments (local, staging, production).
- Loss of "Pure" Statelessness: JWTs were originally designed so the server wouldn't need to track state. By introducing a Redis blacklist and cache, we are moving back to a stateful architecture. However, in modern systems that require immediate revocation and ban enforcement, this hybrid approach is an industry standard.
Proposed Changes
1. Introduce Redis Infrastructure & Production Docker Setup
Since your production environment runs on a server using Docker containers, we will add Redis as a new service in your Docker topology.
[NEW] Dependencies & Config
- Add
spring-boot-starter-data-redis to backend/pom.xml.
- Create
backend/src/main/java/com/swipelab/config/RedisConfig.java to configure the RedisTemplate.
[MODIFY] Production & Local docker-compose.yml
- Add a new
redis service block to your Docker compose files:
redis:
image: redis:7-alpine
container_name: swipelab-redis
command: redis-server --appendonly yes # Ensures active sessions survive container restarts
volumes:
- redis_data:/data
networks:
- app-network
restart: unless-stopped
- Networking: Ensure your backend container has the
SPRING_REDIS_HOST=redis and SPRING_REDIS_PORT=6379 environment variables set so it can communicate with Redis over Docker's internal network.
2. Multi-Session Support via Redis (Fixing Single Session & Bcrypt Issues)
Instead of creating a new RefreshToken PostgreSQL table, we will store refresh tokens directly in Redis.
[MODIFY] backend/src/main/java/com/swipelab/users/domain/User.java
- [DELETE] Remove
refreshTokenHash field.
- (No new DB columns required).
[MODIFY] backend/src/main/java/com/swipelab/auth/application/JwtService.java
- Change
generateRefreshToken to generate a secure random UUID string (instead of a JWT).
- Store this UUID in Redis as a key:
refresh_token:{uuid} -> value: {username}. Set the TTL to the refresh token expiration time (e.g., 7 days).
- Security Win: UUIDs are inherently secure, and since they are stored in a private Redis instance (not the DB), they don't necessarily need hashing. Even if we hash them, we bypass the Bcrypt truncation issue completely.
- Remove old Postgres refresh token logic.
3. Immediate Access Token Revocation (Blacklist)
To ensure logged-out or banned users are stopped instantly.
[NEW] backend/src/main/java/com/swipelab/auth/infrastructure/TokenBlacklistService.java
- Service to add a JWT's signature (or JTI claim if we add one) to Redis:
blacklist:{signature} -> revoked, with a TTL matching the token's remaining lifespan.
[MODIFY] backend/src/main/java/com/swipelab/auth/application/AuthenticationService.java
logout() will now:
- Delete the user's provided refresh token from Redis.
- Extract the current Access Token from the request context and add it to the Redis Blacklist.
4. Stateless Authorization + Fast Ban Checks (Removing DB Hits)
[MODIFY] backend/src/main/java/com/swipelab/auth/infrastructure/JwtAuthenticationFilter.java
- Construct the
Authentication object using roles directly extracted from the JWT payload.
- Query Redis to check if the Access Token is in the blacklist. If yes, reject immediately.
- Remove the call to
userDetailsService.loadUserByUsername(username).
[MODIFY] backend/src/main/java/com/swipelab/auth/infrastructure/BannedUserFilter.java
- Instead of querying
userRepository.findByUsername(username), query Redis for a ban:{username} key.
- (When a user is banned via the Admin API, the admin service must push this key to Redis).
5. Securing Swagger UI
[MODIFY] backend/src/main/java/com/swipelab/auth/infrastructure/SecurityConfig.java
- Change the conditional logic for exposing
/swagger-ui/** and /v3/api-docs/**.
- Only expose these endpoints if
Arrays.asList(env.getActiveProfiles()).contains("dev") || Arrays.asList(env.getActiveProfiles()).contains("local").
Verification Plan
Automated Tests
- Introduce Testcontainers for Redis in integration tests.
- Add tests to ensure
JwtService issues refresh tokens to Redis and retrieves them correctly.
- Add tests for
TokenBlacklistService to verify blacklisted tokens are rejected by the JwtAuthenticationFilter.
- Add tests for
BannedUserFilter to verify it correctly intercepts requests when the ban key is present in Redis.
Manual Verification
- Start the application with the new
docker-compose.yml (including Redis).
- Log in from two different terminals (simulating two devices) and verify both receive unique refresh tokens and can rotate them independently.
- Log out on one terminal, and immediately attempt to use the Access Token. Verify it is rejected (401 Unauthorized) because it is in the Redis blacklist.
- Manually add a ban key to Redis via
redis-cli, then attempt to use a valid Access Token. Verify the BannedUserFilter rejects the request.
- Verify Swagger UI is securely hidden when not using dev/local profiles.
Backend Authentication Hardening Plan (Redis Architecture)
This plan addresses the critical security and performance issues identified in the authentication flow by introducing Redis as a high-performance, in-memory datastore for session management, token revocation, and caching.
Why Use Redis? (Tradeoffs)
The Good (Why it's beneficial)
ban:{username}key). TheBannedUserFiltercan check this key in sub-millisecond time, enforcing bans immediately without slamming PostgreSQL on every API request.The Bad (Why it's a tradeoff)
Proposed Changes
1. Introduce Redis Infrastructure & Production Docker Setup
Since your production environment runs on a server using Docker containers, we will add Redis as a new service in your Docker topology.
[NEW] Dependencies & Config
spring-boot-starter-data-redistobackend/pom.xml.backend/src/main/java/com/swipelab/config/RedisConfig.javato configure theRedisTemplate.[MODIFY] Production & Local
docker-compose.ymlredisservice block to your Docker compose files:SPRING_REDIS_HOST=redisandSPRING_REDIS_PORT=6379environment variables set so it can communicate with Redis over Docker's internal network.2. Multi-Session Support via Redis (Fixing Single Session & Bcrypt Issues)
Instead of creating a new
RefreshTokenPostgreSQL table, we will store refresh tokens directly in Redis.[MODIFY]
backend/src/main/java/com/swipelab/users/domain/User.javarefreshTokenHashfield.[MODIFY]
backend/src/main/java/com/swipelab/auth/application/JwtService.javagenerateRefreshTokento generate a secure random UUID string (instead of a JWT).refresh_token:{uuid}-> value:{username}. Set the TTL to the refresh token expiration time (e.g., 7 days).3. Immediate Access Token Revocation (Blacklist)
To ensure logged-out or banned users are stopped instantly.
[NEW]
backend/src/main/java/com/swipelab/auth/infrastructure/TokenBlacklistService.javablacklist:{signature}->revoked, with a TTL matching the token's remaining lifespan.[MODIFY]
backend/src/main/java/com/swipelab/auth/application/AuthenticationService.javalogout()will now:4. Stateless Authorization + Fast Ban Checks (Removing DB Hits)
[MODIFY]
backend/src/main/java/com/swipelab/auth/infrastructure/JwtAuthenticationFilter.javaAuthenticationobject using roles directly extracted from the JWT payload.userDetailsService.loadUserByUsername(username).[MODIFY]
backend/src/main/java/com/swipelab/auth/infrastructure/BannedUserFilter.javauserRepository.findByUsername(username), query Redis for aban:{username}key.5. Securing Swagger UI
[MODIFY]
backend/src/main/java/com/swipelab/auth/infrastructure/SecurityConfig.java/swagger-ui/**and/v3/api-docs/**.Arrays.asList(env.getActiveProfiles()).contains("dev") || Arrays.asList(env.getActiveProfiles()).contains("local").Verification Plan
Automated Tests
JwtServiceissues refresh tokens to Redis and retrieves them correctly.TokenBlacklistServiceto verify blacklisted tokens are rejected by theJwtAuthenticationFilter.BannedUserFilterto verify it correctly intercepts requests when the ban key is present in Redis.Manual Verification
docker-compose.yml(including Redis).redis-cli, then attempt to use a valid Access Token. Verify theBannedUserFilterrejects the request.