mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-03 04:40:09 +00:00
Initial commit
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
# Route Authentication and Protection
|
||||
|
||||
**Status:** ✅ Implemented | Token expiry validation, auto-refresh, 401 handling
|
||||
|
||||
Authentication and authorization system protecting routes, ensuring only authenticated users can access protected pages.
|
||||
|
||||
## Protection Strategy
|
||||
|
||||
**Client-Side:** React components check auth state, redirect to login if needed, preserve original URL
|
||||
**Server-Side:** API routes validate JWT tokens via middleware, return 401/403 for unauthorized
|
||||
|
||||
## Routes
|
||||
|
||||
**Public:** `/login`, `/setup`, `/api/*` (handle auth independently)
|
||||
**Protected:** `/` (home), `/search`, `/requests`, `/profile`
|
||||
**Admin:** `/admin/*` - requires admin role
|
||||
|
||||
## ProtectedRoute Component
|
||||
|
||||
**Location:** `src/components/auth/ProtectedRoute.tsx`
|
||||
|
||||
**Behavior:**
|
||||
1. Check auth state from AuthContext
|
||||
2. Optionally check admin role
|
||||
3. Show loading spinner while checking
|
||||
4. Redirect to `/login` if unauthenticated
|
||||
5. Redirect to `/` if admin required but not admin
|
||||
6. Render children if authorized
|
||||
|
||||
## API Middleware
|
||||
|
||||
**Location:** `src/lib/middleware/auth.ts`
|
||||
|
||||
**Server-side validation:**
|
||||
- `requireAuth()` - validates JWT, adds user to request
|
||||
- `requireAdmin()` - checks admin role, chains after requireAuth
|
||||
- Returns 401 for invalid/expired tokens
|
||||
- Returns 403 for insufficient permissions
|
||||
|
||||
## Token Management
|
||||
|
||||
**Location:** `src/contexts/AuthContext.tsx`, `src/lib/utils/jwt-client.ts`
|
||||
|
||||
**Token Validation on Mount:**
|
||||
- Decodes access token to check expiry
|
||||
- If expired but refresh token valid → auto-refresh
|
||||
- If both expired → clear storage, redirect to login
|
||||
- Cross-tab logout sync via storage events
|
||||
|
||||
**Auto-Refresh (5 mins before expiry):**
|
||||
```typescript
|
||||
const refreshTimeMs = getRefreshTimeMs(token);
|
||||
setTimeout(() => refreshToken(), refreshTimeMs);
|
||||
```
|
||||
|
||||
**Schedule:**
|
||||
- After login → schedule first refresh
|
||||
- After token refresh → schedule next refresh
|
||||
- Cleanup on logout or unmount
|
||||
|
||||
## API Client with 401 Handling
|
||||
|
||||
**Location:** `src/lib/utils/api.ts`
|
||||
|
||||
**fetchWithAuth():**
|
||||
- Adds Authorization header automatically
|
||||
- Catches 401 responses
|
||||
- Attempts token refresh once
|
||||
- Retries original request with new token
|
||||
- Logs out if refresh fails
|
||||
- Prevents duplicate refresh requests
|
||||
|
||||
**Usage:**
|
||||
```typescript
|
||||
// In hooks/components
|
||||
import { fetchWithAuth, fetchJSON } from '@/lib/utils/api';
|
||||
|
||||
// GET request
|
||||
const response = await fetchWithAuth('/api/requests');
|
||||
|
||||
// POST with JSON
|
||||
const data = await fetchJSON('/api/requests', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ audiobook }),
|
||||
});
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
**401 Unauthorized:**
|
||||
1. Attempt token refresh automatically
|
||||
2. Retry original request with new token
|
||||
3. If still 401 or refresh fails → logout (clears storage + redirects to /login)
|
||||
|
||||
**403 Forbidden:**
|
||||
- Valid token but insufficient permissions
|
||||
- Return error, don't logout
|
||||
|
||||
## Logout Behavior
|
||||
|
||||
**Global redirect on logout:**
|
||||
- `logout()` from AuthContext → clears storage + redirects to /login
|
||||
- API 401 errors → `performLogout()` → clears storage + redirects to /login
|
||||
- Cross-tab logout → storage event triggers redirect to /login
|
||||
- Ensures user never remains on authenticated pages after logout
|
||||
|
||||
## Cross-Tab Sync
|
||||
|
||||
**Storage Events:**
|
||||
- Logout in one tab → logout + redirect to login in all tabs
|
||||
- Login in one tab → sync auth state to all tabs
|
||||
- Prevents stale sessions across browser tabs
|
||||
|
||||
## Security
|
||||
|
||||
- Never log tokens
|
||||
- HTTPS only in production
|
||||
- Short access token expiry (1hr)
|
||||
- Auto-refresh 5 mins before expiry
|
||||
- Token expiry validation on mount
|
||||
- Prevent duplicate refresh requests
|
||||
- SameSite cookies for CSRF protection
|
||||
- Client-side token decode (signature verified server-side only)
|
||||
|
||||
## Fixed Issues
|
||||
|
||||
- **Expired tokens not logging out:** Added token expiry validation on mount
|
||||
- **No auto-refresh:** Scheduled refresh 5 mins before token expires
|
||||
- **401 errors not handled:** Added global 401 interceptor with token refresh
|
||||
- **Logged-out sessions persisting:** Token validation clears expired sessions immediately
|
||||
- **Logout not redirecting:** Added automatic redirect to /login on all logout scenarios (manual, API 401, cross-tab)
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- Next.js 14+ App Router
|
||||
- JWT via AuthContext
|
||||
- React Context API
|
||||
- Custom fetch wrapper for 401 handling
|
||||
Reference in New Issue
Block a user