Skip to main content

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:

  1. End-user OAuth: Users authorize their own Gmail accounts
  2. Service Account: Server-to-server for backend operations
  3. 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

ScopePurposeRequired
gmail.sendSend emailsYes
gmail.readonlyRead emailsYes
gmail.modifyAdd/remove labels, mark readOptional
gmail.labelsManage labelsOptional

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

  1. Token Encryption: AES-256 at rest
  2. Secure Storage: Encrypted file or database
  3. Token Rotation: Automatic refresh before expiry
  4. Revocation Support: Immediate token revocation
  5. 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 CaseRequired Scopes
Send onlygmail.send
Read onlygmail.readonly
Full accessAll 4 scopes
Notificationsgmail.readonly, gmail.modify
  • 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