348 lines
8.3 KiB
Markdown
348 lines
8.3 KiB
Markdown
## 🚀 Authentication Fix - Deployment Guide
|
|
|
|
**IMPORTANT**: Follow these steps in order to avoid downtime.
|
|
|
|
---
|
|
|
|
## 📋 Pre-Deployment Checklist
|
|
|
|
- [ ] Backup database
|
|
- [ ] Review all changes in [AUTHENTICATION-FIX-IMPLEMENTATION.md](AUTHENTICATION-FIX-IMPLEMENTATION.md)
|
|
- [ ] Read [AUTHENTICATION-AUDIT-REPORT.md](AUTHENTICATION-AUDIT-REPORT.md) for context
|
|
- [ ] Ensure Redis is running
|
|
- [ ] Test in staging environment first (if available)
|
|
|
|
---
|
|
|
|
## 🔧 Step-by-Step Deployment
|
|
|
|
### Step 1: Install Backend Dependencies
|
|
|
|
```bash
|
|
cd /data/app/igny8/backend
|
|
pip install -r requirements.txt
|
|
# Specifically: django-redis>=5.4.0
|
|
```
|
|
|
|
### Step 2: Run Database Migrations
|
|
|
|
```bash
|
|
cd /data/app/igny8/backend
|
|
python manage.py makemigrations
|
|
python manage.py migrate
|
|
|
|
# You should see a new migration for RefreshToken model:
|
|
# - Create model RefreshToken
|
|
# - Add indexes
|
|
```
|
|
|
|
### Step 3: Verify Redis is Running
|
|
|
|
```bash
|
|
# Check Redis is accessible
|
|
redis-cli -h redis ping
|
|
# Should return: PONG
|
|
|
|
# Or check via Docker:
|
|
docker ps | grep redis
|
|
```
|
|
|
|
### Step 4: Test Backend Changes (Optional but Recommended)
|
|
|
|
```bash
|
|
cd /data/app/igny8/backend
|
|
python manage.py shell
|
|
|
|
# In the shell:
|
|
from igny8_core.auth.models_refresh_token import RefreshToken
|
|
from django.contrib.auth import get_user_model
|
|
User = get_user_model()
|
|
|
|
# Test token creation:
|
|
user = User.objects.first()
|
|
token = RefreshToken.create_token(user, remember_me=True)
|
|
print(f"Created token: {token.token_id}")
|
|
print(f"Expires: {token.expires_at}")
|
|
print(f"Valid: {token.is_valid}")
|
|
```
|
|
|
|
### Step 5: Restart Backend
|
|
|
|
```bash
|
|
cd /data/app/igny8
|
|
docker-compose -f docker-compose.app.yml restart igny8_backend
|
|
|
|
# Wait for healthy status:
|
|
docker-compose -f docker-compose.app.yml ps igny8_backend
|
|
```
|
|
|
|
### Step 6: Replace Frontend API Service
|
|
|
|
```bash
|
|
cd /data/app/igny8/frontend/src/services
|
|
|
|
# Backup old api.ts
|
|
cp api.ts api-old-backup.ts
|
|
|
|
# Replace with new implementation
|
|
mv api-new.ts api.ts
|
|
```
|
|
|
|
### Step 7: Update Frontend authStore (CRITICAL)
|
|
|
|
Open `frontend/src/store/authStore.ts` and make these changes:
|
|
|
|
#### 7a. Remove localStorage access_token persistence
|
|
|
|
Find and REMOVE these lines from login/register:
|
|
```typescript
|
|
// ❌ REMOVE THIS:
|
|
localStorage.setItem('access_token', newToken);
|
|
localStorage.setItem('refresh_token', newRefreshToken);
|
|
```
|
|
|
|
Access token should ONLY be in Zustand state (memory).
|
|
|
|
#### 7b. Update persist configuration
|
|
|
|
Ensure `partialize` does NOT include `token`:
|
|
```typescript
|
|
partialize: (state) => ({
|
|
user: state.user,
|
|
// ❌ DO NOT persist token: state.token,
|
|
refreshToken: state.refreshToken,
|
|
isAuthenticated: state.isAuthenticated,
|
|
})
|
|
```
|
|
|
|
#### 7c. Update refresh logic
|
|
|
|
Ensure refresh updates BOTH access and refresh tokens:
|
|
```typescript
|
|
// In refreshToken action:
|
|
const data = await response.json();
|
|
set({
|
|
token: data.data?.access || data.access,
|
|
refreshToken: data.data?.refresh || data.refresh // NEW: update refresh token too
|
|
});
|
|
```
|
|
|
|
#### 7d. Remove proactive refresh interval (Optional)
|
|
|
|
The new api.ts handles token refresh automatically on 401. Remove this from AppLayout.tsx:
|
|
```typescript
|
|
// ❌ REMOVE OR COMMENT OUT:
|
|
// const tokenRefreshInterval = setInterval(async () => {
|
|
// await authState.refreshToken();
|
|
// }, 720000);
|
|
```
|
|
|
|
### Step 8: Add Remember-Me Checkbox to Login Form
|
|
|
|
Open your login form component (e.g., `pages/SignIn.tsx`) and add:
|
|
|
|
```typescript
|
|
// Add state:
|
|
const [rememberMe, setRememberMe] = useState(false);
|
|
|
|
// Add checkbox before submit button:
|
|
<label className="flex items-center gap-2">
|
|
<input
|
|
type="checkbox"
|
|
checked={rememberMe}
|
|
onChange={(e) => setRememberMe(e.target.checked)}
|
|
className="form-checkbox"
|
|
/>
|
|
<span>Keep me logged in for 20 days</span>
|
|
</label>
|
|
|
|
// Update login call:
|
|
await login(email, password, rememberMe);
|
|
```
|
|
|
|
Update authStore login method signature:
|
|
```typescript
|
|
login: async (email: string, password: string, rememberMe = false) => {
|
|
// ...
|
|
body: JSON.stringify({ email, password, remember_me: rememberMe }),
|
|
// ...
|
|
}
|
|
```
|
|
|
|
### Step 9: Rebuild and Restart Frontend
|
|
|
|
```bash
|
|
cd /data/app/igny8/frontend
|
|
docker-compose -f ../docker-compose.app.yml restart igny8_frontend
|
|
|
|
# Or rebuild if needed:
|
|
docker build -t igny8-frontend-dev:latest -f Dockerfile.dev .
|
|
docker-compose -f ../docker-compose.app.yml up -d igny8_frontend
|
|
```
|
|
|
|
### Step 10: Verify Everything Works
|
|
|
|
#### Test 1: Fresh Login
|
|
1. Clear browser cache and cookies
|
|
2. Navigate to login page
|
|
3. Login with remember-me checked
|
|
4. Verify you stay logged in
|
|
5. Check Redis: `redis-cli keys "*"` should show session
|
|
|
|
#### Test 2: Token Refresh
|
|
1. Login
|
|
2. Wait 1 hour (access token expires)
|
|
3. Make an API call
|
|
4. Should automatically refresh and succeed
|
|
5. Check network tab - should see refresh call
|
|
|
|
#### Test 3: Multi-Tab
|
|
1. Open app in two tabs
|
|
2. Trigger token refresh in one tab
|
|
3. Make API call in other tab
|
|
4. Should work without re-auth
|
|
|
|
#### Test 4: Error Handling
|
|
1. Login
|
|
2. Trigger a 403 error (access denied page)
|
|
3. Verify you are NOT logged out
|
|
4. Navigate to dashboard - should still work
|
|
|
|
#### Test 5: Logout
|
|
1. Login
|
|
2. Click logout
|
|
3. Verify all tabs logout
|
|
4. Check Redis - session should be gone
|
|
|
|
---
|
|
|
|
## 🐛 Troubleshooting
|
|
|
|
### "RefreshToken model not found"
|
|
```bash
|
|
cd /data/app/igny8/backend
|
|
python manage.py migrate
|
|
```
|
|
|
|
### "Redis connection refused"
|
|
```bash
|
|
# Check Redis is running:
|
|
docker ps | grep redis
|
|
docker-compose -f docker-compose.app.yml ps
|
|
|
|
# Restart Redis:
|
|
cd /data/app
|
|
docker-compose -f docker-compose.yml restart redis
|
|
```
|
|
|
|
### "django-redis not installed"
|
|
```bash
|
|
cd /data/app/igny8/backend
|
|
pip install django-redis>=5.4.0
|
|
docker-compose -f docker-compose.app.yml restart igny8_backend
|
|
```
|
|
|
|
### "Still getting logged out on 403 errors"
|
|
- Check that `frontend/src/services/api.ts` is the NEW version
|
|
- Old api.ts has logout on 403, new one doesn't
|
|
- Verify file was replaced: `grep "NEVER logout on 403" frontend/src/services/api.ts`
|
|
|
|
### "Token refresh not working"
|
|
- Check backend logs: `docker logs igny8_backend -f`
|
|
- Verify RefreshToken records in database
|
|
- Check that refresh endpoint returns new refresh token
|
|
|
|
### "Multi-tab logout"
|
|
- Verify BroadcastChannel is supported (Chrome/Firefox/Edge)
|
|
- Check browser console for errors
|
|
- Fallback: Polling localStorage for token changes
|
|
|
|
---
|
|
|
|
## 📊 Monitoring Post-Deployment
|
|
|
|
### Key Metrics to Watch:
|
|
1. **Logout rate**: Should drop to near-zero (except explicit logouts)
|
|
2. **Token refresh success rate**: Should be >99.9%
|
|
3. **403 errors**: Should NOT cause logout
|
|
4. **Session duration**: Average should increase to ~20 days with remember-me
|
|
|
|
### Database Queries:
|
|
```sql
|
|
-- Check active refresh tokens
|
|
SELECT COUNT(*) FROM auth_refresh_token WHERE revoked_at IS NULL AND expires_at > NOW();
|
|
|
|
-- Check token rotation (should increase over time)
|
|
SELECT AVG(rotation_count) FROM auth_refresh_token WHERE created_at > NOW() - INTERVAL '7 days';
|
|
|
|
-- Check remember-me usage
|
|
SELECT
|
|
remember_me,
|
|
COUNT(*) as count,
|
|
AVG(EXTRACT(EPOCH FROM (expires_at - created_at))/86400) as avg_days
|
|
FROM auth_refresh_token
|
|
WHERE created_at > NOW() - INTERVAL '7 days'
|
|
GROUP BY remember_me;
|
|
```
|
|
|
|
---
|
|
|
|
## 🔄 Rollback Plan (If Needed)
|
|
|
|
If something goes wrong:
|
|
|
|
### 1. Restore old api.ts:
|
|
```bash
|
|
cd /data/app/igny8/frontend/src/services
|
|
mv api.ts api-new-broken.ts
|
|
mv api-old-backup.ts api.ts
|
|
```
|
|
|
|
### 2. Revert settings.py changes:
|
|
```python
|
|
# Change back:
|
|
SESSION_COOKIE_SAMESITE = 'Strict'
|
|
SESSION_SAVE_EVERY_REQUEST = False
|
|
JWT_ACCESS_TOKEN_EXPIRY = timedelta(minutes=15)
|
|
# Comment out Redis cache config
|
|
```
|
|
|
|
### 3. Revert middleware.py:
|
|
- Add `from django.contrib.auth import logout` back
|
|
- Restore logout calls in validation failures
|
|
|
|
### 4. Restart services:
|
|
```bash
|
|
docker-compose -f docker-compose.app.yml restart igny8_backend igny8_frontend
|
|
```
|
|
|
|
---
|
|
|
|
## ✅ Success Indicators
|
|
|
|
You'll know the fix is working when:
|
|
|
|
- ✅ Users report no random logouts
|
|
- ✅ Remember-me users stay logged in for 20 days
|
|
- ✅ 403/402 errors don't log users out
|
|
- ✅ Network hiccups don't cause logout
|
|
- ✅ Multi-tab usage works smoothly
|
|
- ✅ Token refresh is invisible to users
|
|
|
|
---
|
|
|
|
## 📞 Support
|
|
|
|
If you encounter issues:
|
|
1. Check logs: `docker logs igny8_backend -f`
|
|
2. Review error messages in browser console
|
|
3. Verify migrations ran successfully
|
|
4. Check Redis connectivity
|
|
5. Test with fresh user account
|
|
|
|
---
|
|
|
|
**Last Updated:** December 15, 2025
|
|
**Estimated Deployment Time:** 30-45 minutes
|
|
**Risk Level:** Low (all changes backward compatible)
|