# Microsoft OAuth Token Management Analysis ## Current Implementation ### Token Storage Locations 1. **Redis Cache** (Primary for OAuth tokens) - **Location**: `lib/redis.ts` → `cacheEmailCredentials()` - **TTL**: 24 hours (`TTL.CREDENTIALS = 60 * 60 * 24`) - **Stored**: `accessToken`, `refreshToken`, `tokenExpiry`, `useOAuth` - **Key Format**: `email:credentials:${userId}:${accountId}` 2. **Prisma Database** (Schema has fields but NOT used for OAuth tokens) - **Location**: `prisma/schema.prisma` → `MailCredentials` model - **Fields Available**: `refresh_token`, `access_token`, `token_expiry`, `use_oauth` - **Current Status**: ❌ **Tokens are NOT saved to Prisma** (only Redis) - **Code Comment**: "OAuth fields don't exist" (but they DO exist in schema!) ### Token Refresh Flow **Location**: `lib/services/token-refresh.ts` → `ensureFreshToken()` 1. Checks Redis for credentials 2. Validates token expiry (5-minute buffer) 3. Refreshes token if needed via Microsoft API 4. **Updates Redis only** (not Prisma) 5. Returns new access token ### Issues Identified #### 🔴 Critical Issue #1: Refresh Tokens Not Persisted to Database **Problem**: - Refresh tokens are only stored in Redis with 24-hour TTL - If Redis is cleared, restarted, or TTL expires, refresh tokens are **permanently lost** - Microsoft refresh tokens can last up to **90 days** (or indefinitely with `offline_access` scope) - Users would need to re-authenticate if Redis data is lost **Impact**: - ❌ Not viable for long-term production use - ❌ Data loss risk on Redis restarts - ❌ No backup/recovery mechanism #### 🟡 Issue #2: Token Refresh Doesn't Update Database **Problem**: - When tokens are refreshed, only Redis is updated - Prisma database still has old/expired tokens (if any) - Schema has the fields but they're never populated **Impact**: - ⚠️ Inconsistency between Redis and Database - ⚠️ Can't recover from Redis cache loss #### 🟡 Issue #3: Missing Refresh Token in Logs From your logs: ``` hasRefreshToken: false ``` This suggests the refresh token might not be properly saved or retrieved. ### Microsoft OAuth Token Lifespan - **Access Token**: ~1 hour (3600 seconds) - **Refresh Token**: Up to 90 days (with `offline_access` scope) - **Token Refresh**: Returns new access token, may return new refresh token ### Required Scopes Current implementation uses: ```typescript const REQUIRED_SCOPES = [ 'offline_access', // ✅ Required for long-lived refresh tokens 'https://outlook.office.com/IMAP.AccessAsUser.All', 'https://outlook.office.com/SMTP.Send' ].join(' '); ``` ✅ `offline_access` is included - this is correct for long-term use. ## Recommendations ### ✅ Fix #1: Persist Refresh Tokens to Prisma **Why**: Refresh tokens are critical for long-term access and should be persisted to database. **Implementation**: 1. Save `refresh_token` to Prisma `MailCredentials.refresh_token` field 2. Update `token_expiry` when tokens are refreshed 3. Keep access tokens in Redis (short-lived, can be regenerated) 4. Use Prisma as source of truth for refresh tokens ### ✅ Fix #2: Update Database on Token Refresh **Why**: Keep database in sync with refreshed tokens. **Implementation**: 1. After refreshing tokens, update Prisma `MailCredentials` record 2. Update `access_token` and `token_expiry` fields 3. Update `refresh_token` if Microsoft returns a new one ### ✅ Fix #3: Fallback to Database if Redis Missing **Why**: Recover from Redis cache loss. **Implementation**: 1. If Redis cache is empty, check Prisma for refresh token 2. Use Prisma refresh token to get new access token 3. Re-populate Redis cache ## Long-Term Viability Assessment ### Current State: ⚠️ **NOT VIABLE** for long-term production **Reasons**: 1. ❌ Refresh tokens only in volatile Redis cache 2. ❌ No persistence mechanism 3. ❌ Risk of data loss on Redis restart 4. ❌ No recovery mechanism ### After Fixes: ✅ **VIABLE** for long-term production **With recommended fixes**: 1. ✅ Refresh tokens persisted to database 2. ✅ Redis used for fast access token retrieval 3. ✅ Database as source of truth 4. ✅ Recovery mechanism in place ## Token Storage Strategy (Recommended) ### Access Tokens - **Storage**: Redis (fast, short-lived) - **TTL**: 1 hour (matches Microsoft token expiry) - **Purpose**: Fast IMAP/SMTP authentication ### Refresh Tokens - **Storage**: Prisma Database (persistent, long-term) - **TTL**: None (stored indefinitely until revoked) - **Purpose**: Long-term access, token renewal ### Token Expiry - **Storage**: Both Redis and Prisma - **Purpose**: Know when to refresh tokens ## Implementation Priority 1. **HIGH**: Persist refresh tokens to Prisma 2. **HIGH**: Update Prisma on token refresh 3. **MEDIUM**: Add fallback to database if Redis missing 4. **LOW**: Add token encryption at rest (if required by compliance)