NeahNew/MICROSOFT_OAUTH_ANALYSIS.md
2026-01-01 19:31:48 +01:00

4.8 KiB

Microsoft OAuth Token Management Analysis

Current Implementation

Token Storage Locations

  1. Redis Cache (Primary for OAuth tokens)

    • Location: lib/redis.tscacheEmailCredentials()
    • 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.prismaMailCredentials 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.tsensureFreshToken()

  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:

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

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)