Skip to content
All writing
Security6 min read

Lessons from Gmail OAuth and production auth

Tokens, scopes, refresh flows, CSRF, and the gap between a login that works on localhost and one you'd trust in production.

OAuth demos in an afternoon and deploys over weeks. The happy path of redirect, consent, callback, token, and fetch is quick to stand up. The distance between that and something you would trust in production with other people's email is where the real work lives. Building Gmail integration for Agentic CRM taught me most of that distance the hard way.

Scopes are a commitment

Every scope you request is a promise about what you will touch and a surface you now have to defend. The temptation is to ask for broad access so nothing breaks later. The discipline is to ask for the least you need, read-only inbox access for a system that reads and proposes, then design around that limit instead of reaching past it. Least privilege here is not a nicety. It decides whether a token leak is contained or catastrophic.

Tokens are living credentials

A token is not a fact you store once. It expires, it refreshes, and it can be revoked at any moment by the user or the provider. Production auth means putting that lifecycle in one place, covering authorization, refresh, and revocation, and treating the tokens as sensitive material: encrypted at rest, never written to logs, never handed to the client. The moment token handling is scattered across the codebase is the moment it stops being something you can reason about.

The state parameter is not ceremony

It is easy to treat the OAuth state parameter as decoration and skip validating it. It exists to tie the callback back to the request that started it and to shut down CSRF on the authorization flow. Handling the exchange server-side, keeping the sensitive steps off the client, follows the same principle. The parts that matter should not run anywhere the user can tamper with them.

Sync has to recover

A login that works on localhost assumes everything succeeds. Production assumes the opposite. Inbox sync runs as background jobs, and those jobs fail. Networks drop, rate limits hit, a refresh races a revocation. The requirement is that a failed sync never loses an email and never duplicates one. That means idempotent, retryable jobs, where re-running converges to the same state instead of corrupting it. Designing for the failure case is most of what separates a demo from a deployment.

Auth is layered, and the gaps are where it breaks

  • JWT and server-side sessions establish who you are. CSRF protection guards state-changing requests. RBAC governs what you can do in a workspace. OAuth scopes limit what the integration can reach.
  • Each piece is straightforward alone. The bugs live in the seams: a mutation that skips CSRF, a worker that runs without the right identity, a scope wider than the feature needs.
  • Security here is not one strong wall. It is several layers that have to fit together without leaving a gap.

The lesson I keep relearning is that the interesting auth work is never the login button. It is the lifecycle, the failure modes, and the boundaries between mechanisms, the parts you only see once you try to actually run the thing.