ADR-003: OAuth Authentication Strategy for Gmail
Status: Accepted Date: December 17, 2025 Decision Makers: CODITECT Engineering Team
Context
Gmail API requires OAuth 2.0 authentication. We need to support multiple authentication scenarios:
- End-user OAuth: Users authorize their own Gmail accounts
- Service Account: Server-to-server for backend operations
- Domain-wide Delegation: Access all users in a Workspace domain
Decision
We will implement a dual OAuth strategy supporting both User OAuth 2.0 and Service Account authentication, with domain-wide delegation as an optional enterprise feature.
Authentication Modes
Mode 1: User OAuth 2.0 (Primary)
For individual users authorizing their Gmail accounts.
class GmailOAuthManager:
"""User OAuth 2.0 flow manager."""
SCOPES = [
'https://www.googleapis.com/auth/gmail.send',
'https://www.googleapis.com/auth/gmail.readonly',
'https://www.googleapis.com/auth/gmail.modify',
'https://www.googleapis.com/auth/gmail.labels',
]
def get_authorization_url(self, state: str) -> str:
"""Generate consent screen URL."""
...
async def exchange_code(self, code: str, user_id: str) -> Credentials:
"""Exchange auth code for tokens."""
...
async def refresh_token(self, user_id: str) -> Credentials:
"""Refresh expired access token."""
...
Mode 2: Service Account with Domain-Wide Delegation
For enterprise Workspace customers.
class GmailServiceAccountAuth:
"""Service account with domain-wide delegation."""
def __init__(
self,
service_account_file: str,
):
self._base_credentials = service_account.Credentials.from_service_account_file(
service_account_file,
scopes=SCOPES,
)
def get_credentials_for_user(self, user_email: str) -> Credentials:
"""Impersonate a domain user."""
return self._base_credentials.with_subject(user_email)
OAuth Scopes
| Scope | Purpose | Required |
|---|---|---|
gmail.send | Send emails | Yes |
gmail.readonly | Read emails | Yes |
gmail.modify | Add/remove labels, mark read | Optional |
gmail.labels | Manage labels | Optional |
Token Storage
class TokenStorage(Protocol):
"""Secure token storage interface."""
async def save(self, user_id: str, credentials: Credentials) -> None:
"""Save credentials with encryption."""
...
async def load(self, user_id: str) -> Optional[Credentials]:
"""Load and decrypt credentials."""
...
async def delete(self, user_id: str) -> None:
"""Securely delete credentials."""
...
Security Requirements
- Token Encryption: AES-256 at rest
- Secure Storage: Encrypted file or database
- Token Rotation: Automatic refresh before expiry
- Revocation Support: Immediate token revocation
- Audit Logging: Log all auth events
Consequences
Positive
- Flexible Auth: Supports both individual and enterprise
- Secure: Industry-standard OAuth 2.0
- User Consent: Explicit user authorization
- Token Management: Automatic refresh handling
Negative
- Complexity: Two auth flows to maintain
- Domain-Wide Setup: Requires Workspace admin configuration
- Consumer Limitations: Some features need Workspace
Mitigation
- Unified auth manager selects mode automatically
- Clear documentation for Workspace setup
- Graceful fallback for missing permissions
Scope Selection Strategy
| Use Case | Required Scopes |
|---|---|
| Send only | gmail.send |
| Read only | gmail.readonly |
| Full access | All 4 scopes |
| Notifications | gmail.readonly, gmail.modify |
Related Decisions
- ADR-001: Email Provider Abstraction
- ADR-002: Zero-Cost Architecture
- Google Meet ADR-003: OAuth Strategy (parallel pattern)
Document Control:
- Created: December 17, 2025
- Author: CODITECT Engineering Team