4.8 KiB
4.8 KiB
Microsoft OAuth Token Management Analysis
Current Implementation
Token Storage Locations
-
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}
- Location:
-
Prisma Database (Schema has fields but NOT used for OAuth tokens)
- Location:
prisma/schema.prisma→MailCredentialsmodel - 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!)
- Location:
Token Refresh Flow
Location: lib/services/token-refresh.ts → ensureFreshToken()
- Checks Redis for credentials
- Validates token expiry (5-minute buffer)
- Refreshes token if needed via Microsoft API
- Updates Redis only (not Prisma)
- 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_accessscope) - 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_accessscope) - Token Refresh: Returns new access token, may return new refresh token
Required Scopes
Current implementation uses:
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:
- Save
refresh_tokento PrismaMailCredentials.refresh_tokenfield - Update
token_expirywhen tokens are refreshed - Keep access tokens in Redis (short-lived, can be regenerated)
- 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:
- After refreshing tokens, update Prisma
MailCredentialsrecord - Update
access_tokenandtoken_expiryfields - Update
refresh_tokenif Microsoft returns a new one
✅ Fix #3: Fallback to Database if Redis Missing
Why: Recover from Redis cache loss.
Implementation:
- If Redis cache is empty, check Prisma for refresh token
- Use Prisma refresh token to get new access token
- Re-populate Redis cache
Long-Term Viability Assessment
Current State: ⚠️ NOT VIABLE for long-term production
Reasons:
- ❌ Refresh tokens only in volatile Redis cache
- ❌ No persistence mechanism
- ❌ Risk of data loss on Redis restart
- ❌ No recovery mechanism
After Fixes: ✅ VIABLE for long-term production
With recommended fixes:
- ✅ Refresh tokens persisted to database
- ✅ Redis used for fast access token retrieval
- ✅ Database as source of truth
- ✅ 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
- HIGH: Persist refresh tokens to Prisma
- HIGH: Update Prisma on token refresh
- MEDIUM: Add fallback to database if Redis missing
- LOW: Add token encryption at rest (if required by compliance)