250 lines
9.1 KiB
Markdown
250 lines
9.1 KiB
Markdown
# Session Handling Analysis for Horizontal Scaling
|
|
|
|
## Executive Summary
|
|
|
|
**✅ YES, your application is STATELESS and ready for horizontal scaling with load balancers.**
|
|
|
|
The session handling architecture is designed to work across multiple gateway instances without requiring sticky sessions or shared in-memory storage. Users will **NOT** lose their sessions when API calls hit different gateway instances.
|
|
|
|
---
|
|
|
|
## Architecture Overview
|
|
|
|
### Backend Session Management (Gateway)
|
|
|
|
#### 1. **JWT Token-Based Authentication**
|
|
- **Location**: `gateway/modules/auth/authentication.py`
|
|
- **Token Storage**: JWT tokens are stored in **httpOnly cookies** (`auth_token` and `refresh_token`)
|
|
- **Token Format**: Self-contained JWT tokens with claims including:
|
|
- `sub` (username)
|
|
- `userId`
|
|
- `mandateId`
|
|
- `jti` (token ID)
|
|
- `sid` (session ID)
|
|
- `authenticationAuthority`
|
|
- `exp` (expiration)
|
|
|
|
#### 2. **Database-Backed Token Validation**
|
|
- **Location**: `gateway/modules/interfaces/interfaceDbAppObjects.py`
|
|
- **Token Table**: All tokens are stored in a `Token` database table with fields:
|
|
- `id` (jti - token ID)
|
|
- `userId`
|
|
- `authority`
|
|
- `sessionId`
|
|
- `mandateId`
|
|
- `status` (ACTIVE/REVOKED)
|
|
- `expiresAt`
|
|
- `revokedAt`, `revokedBy`, `reason`
|
|
|
|
- **Validation Process** (per request):
|
|
1. JWT token is extracted from httpOnly cookie or Authorization header
|
|
2. Token is decoded and validated (signature, expiration)
|
|
3. Token ID (`jti`) is extracted from the JWT payload
|
|
4. **Database query** is performed to verify:
|
|
- Token exists in database
|
|
- Token status is ACTIVE
|
|
- Token matches user, session, and mandate context
|
|
5. User is retrieved from database based on token claims
|
|
|
|
**Key Code Reference** (`gateway/modules/auth/authentication.py:141-191`):
|
|
```python
|
|
# For LOCAL gateway JWTs, enforce DB-backed token validity and revocation
|
|
if tokenId:
|
|
db_tokens = dbApp.getRecordset(Token, recordFilter={"id": tokenId})
|
|
|
|
if db_tokens:
|
|
db_token = db_tokens[0]
|
|
token_authority = str(db_token.get("authority", "")).lower()
|
|
if token_authority == str(AuthAuthority.LOCAL.value):
|
|
# Must be active and match user/session/mandate
|
|
active_token = appInterface.findActiveTokenById(
|
|
tokenId=tokenId,
|
|
userId=user.id,
|
|
authority=AuthAuthority.LOCAL,
|
|
sessionId=sessionId,
|
|
mandateId=str(mandateId) if mandateId else None,
|
|
)
|
|
if not active_token:
|
|
raise credentialsException
|
|
```
|
|
|
|
#### 3. **No In-Memory Session Storage**
|
|
- ✅ **No Redis** - No Redis or similar caching layer found
|
|
- ✅ **No Memcached** - No memcached usage found
|
|
- ✅ **No In-Memory Sessions** - All session state is in the database
|
|
- ✅ **Stateless Design** - Each request is independently validated
|
|
|
|
#### 4. **Session Management**
|
|
- **Session ID**: Generated on login (`uuid.uuid4()`) and stored in:
|
|
- JWT token claim (`sid`)
|
|
- Token database record (`sessionId`)
|
|
- **Logout**: Revokes all tokens for a session by updating database records (sets `status=REVOKED`)
|
|
- **Token Refresh**: Creates new tokens and stores them in database
|
|
|
|
**Key Code Reference** (`gateway/modules/routes/routeSecurityLocal.py:92-131`):
|
|
```python
|
|
# Create session id and include in token claims for session-scoped logout
|
|
session_id = str(uuid.uuid4())
|
|
token_data["sid"] = session_id
|
|
|
|
# Create access token + set cookie
|
|
access_token, _access_expires = createAccessToken(token_data)
|
|
setAccessTokenCookie(response, access_token)
|
|
|
|
# Save access token to database
|
|
token = Token(
|
|
id=jti,
|
|
userId=user.id,
|
|
authority=AuthAuthority.LOCAL,
|
|
tokenAccess=access_token,
|
|
tokenType="bearer",
|
|
expiresAt=expires_at.timestamp(),
|
|
sessionId=session_id,
|
|
mandateId=str(user.mandateId)
|
|
)
|
|
userInterface.saveAccessToken(token)
|
|
```
|
|
|
|
### Frontend Session Management
|
|
|
|
#### 1. **Cookie-Based Token Storage**
|
|
- **Location**: `frontend_agents/public/js/security/auth.js` and `frontend_agents/public/js/shared/apiCalls.js`
|
|
- **Storage Method**: Tokens are stored in **httpOnly cookies** (not localStorage or sessionStorage)
|
|
- **Automatic Transmission**: Cookies are automatically sent with requests using `credentials: 'include'`
|
|
|
|
**Key Code Reference** (`frontend_agents/public/js/shared/apiCalls.js:151-153`):
|
|
```javascript
|
|
// Note: With httpOnly cookies, we don't need to manually add Authorization header
|
|
// The browser automatically includes cookies with credentials: 'include'
|
|
```
|
|
|
|
#### 2. **CSRF Token Storage**
|
|
- **Location**: `sessionStorage` (client-side only)
|
|
- **Purpose**: CSRF protection, not session state
|
|
- **Note**: CSRF tokens can be regenerated if lost, so this doesn't affect session persistence
|
|
|
|
---
|
|
|
|
## Horizontal Scaling Compatibility
|
|
|
|
### ✅ **Fully Compatible - No Issues**
|
|
|
|
#### Why It Works:
|
|
|
|
1. **Stateless Backend**
|
|
- Each gateway instance validates tokens independently
|
|
- No shared in-memory state between instances
|
|
- All state is in the shared database
|
|
|
|
2. **Database as Single Source of Truth**
|
|
- Token validation queries the database on every request
|
|
- Token revocation updates the database
|
|
- All instances see the same token state
|
|
|
|
3. **Cookie-Based Tokens**
|
|
- Cookies are sent by the browser to whichever instance handles the request
|
|
- No server-side session storage needed
|
|
- Load balancer doesn't need sticky sessions
|
|
|
|
4. **JWT Self-Contained Claims**
|
|
- Token contains all necessary user context
|
|
- Database validation ensures token hasn't been revoked
|
|
- No need to look up session state from another instance
|
|
|
|
### Load Balancer Configuration
|
|
|
|
**Recommended Settings:**
|
|
- ✅ **Session Affinity**: **NOT REQUIRED** (can use round-robin or least-connections)
|
|
- ✅ **Health Checks**: Standard HTTP health checks
|
|
- ✅ **Cookie Handling**: No special configuration needed (browser handles cookies automatically)
|
|
|
|
### Potential Considerations
|
|
|
|
#### 1. **Database Connection Pooling**
|
|
- Ensure each gateway instance has proper database connection pooling
|
|
- Database should handle concurrent connections from multiple instances
|
|
- **Status**: ✅ Should work fine if database is configured for multiple connections
|
|
|
|
#### 2. **CSRF Token Regeneration**
|
|
- CSRF tokens stored in `sessionStorage` may be lost if user switches instances
|
|
- **Impact**: Minimal - CSRF tokens are regenerated automatically
|
|
- **Code Reference**: `frontend_agents/public/js/shared/apiCalls.js:186-203` handles CSRF token generation
|
|
|
|
#### 3. **Token Refresh Race Conditions**
|
|
- If multiple requests refresh tokens simultaneously, ensure database handles concurrent updates
|
|
- **Status**: ✅ Current implementation uses database transactions (via `saveAccessToken`)
|
|
|
|
#### 4. **Cookie Domain and Path**
|
|
- Ensure cookies are set with correct domain/path for load balancer
|
|
- **Current Settings** (`gateway/modules/auth/jwtService.py:58-66`):
|
|
- `path="/"` ✅
|
|
- `samesite="strict"` ✅
|
|
- `httponly=True` ✅
|
|
- `secure` (based on HTTPS) ✅
|
|
|
|
---
|
|
|
|
## Testing Recommendations
|
|
|
|
### 1. **Multi-Instance Test**
|
|
- Deploy 2+ gateway instances behind a load balancer
|
|
- Login on one instance
|
|
- Make requests that hit different instances
|
|
- Verify session persists across instances
|
|
|
|
### 2. **Token Revocation Test**
|
|
- Login on instance A
|
|
- Logout on instance B
|
|
- Verify token is revoked (cannot make requests on instance A)
|
|
|
|
### 3. **Concurrent Request Test**
|
|
- Make multiple simultaneous requests
|
|
- Verify all requests succeed regardless of which instance handles them
|
|
|
|
### 4. **Database Connection Test**
|
|
- Monitor database connections from multiple instances
|
|
- Verify connection pooling works correctly
|
|
- Check for connection leaks
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
| Aspect | Status | Notes |
|
|
|--------|--------|-------|
|
|
| **Stateless Backend** | ✅ YES | No in-memory session storage |
|
|
| **Database-Backed** | ✅ YES | All token state in database |
|
|
| **Cookie-Based** | ✅ YES | httpOnly cookies, auto-sent by browser |
|
|
| **Load Balancer Ready** | ✅ YES | No sticky sessions needed |
|
|
| **Horizontal Scaling** | ✅ READY | Can scale to multiple instances |
|
|
|
|
### Conclusion
|
|
|
|
**Your application is fully ready for horizontal scaling.** The session handling architecture is stateless and database-backed, which means:
|
|
|
|
1. ✅ Users will **NOT** lose sessions when requests hit different instances
|
|
2. ✅ Load balancer can use **round-robin** or **least-connections** (no sticky sessions needed)
|
|
3. ✅ Token validation works independently on each instance
|
|
4. ✅ Token revocation works across all instances (via database)
|
|
|
|
The only shared state is in the database, which you've confirmed will be a single logical instance. This is the correct architecture for horizontal scaling.
|
|
|
|
---
|
|
|
|
## Files Analyzed
|
|
|
|
### Backend
|
|
- `gateway/modules/auth/authentication.py` - Token validation
|
|
- `gateway/modules/auth/jwtService.py` - JWT creation and cookie management
|
|
- `gateway/modules/routes/routeSecurityLocal.py` - Login/logout endpoints
|
|
- `gateway/modules/interfaces/interfaceDbAppObjects.py` - Token database operations
|
|
- `gateway/modules/datamodels/datamodelSecurity.py` - Token data model
|
|
|
|
### Frontend
|
|
- `frontend_agents/public/js/security/auth.js` - Authentication logic
|
|
- `frontend_agents/public/js/shared/apiCalls.js` - API calls with cookie handling
|
|
|
|
---
|
|
|
|
*Analysis Date: 2025-01-27*
|
|
*Analyzed by: AI Assistant*
|