DEV Community

DaltonReed1289
DaltonReed1289

Posted on

Enterprise OAuth Login: Discovery, Handoff, and Callback Boundaries

Enterprise OAuth Login: Discovery, Handoff, and Callback Boundaries

Short answer: keep the identity provider at the authentication boundary, keep account and permission decisions in your application, and make every callback prove which login attempt it belongs to. For a fintech product adding Google and GitHub sign-in, that boundary reduces account-takeover risk without turning the login screen into a maze.

The expensive part is not the redirect. It is the state you retain around it: authorization attempts, callback payloads, audit records, and labels that multiply every query. I treat those records as bytes on a bill and as cardinality in an incident search. A design that stores everything forever can be harder to operate than the OAuth exchange itself.

What the login boundary must own

An external provider answers one question: did this provider authenticate this person for this request? It does not decide which internal customer record receives a wire transfer, which role that customer has, or whether a dormant account may be reopened. Those decisions remain in the service that owns users, entitlements, and session policy.

The practical flow has three distinct moments. First, read the available providers and present only the choices your policy allows. Second, create an authorization handoff with a state value tied to the initiating browser, redirect URI, provider, and an expiry. Third, accept the callback once, validate its state and provider response, then resolve the external identity to an existing internal user or an explicitly approved enrollment path.

That separation matters for Google and GitHub alike. A provider account can change email addresses; an email can be shared or unverified; a user can belong to more than one organization. Match on a stable provider subject plus provider name, then apply your own account-linking rules. Do not let a successful provider response silently create administrator privileges.

How should provider discovery shape authorization handoff and callback ownership?

Discovery is a policy input, not a menu you blindly mirror. Before rendering a button, fetch the provider list from the capability your auth boundary exposes. Record the selected provider in the short-lived login context, and generate the authorization URL from that same context. The callback handler should look up the context by a single-use state token, enforce its age, and mark it consumed in the same transaction that establishes the session.

The callback endpoint is therefore owned by your application even when a platform service performs the provider exchange. Your handler decides where the browser goes next, which internal user is attached, and what to do when the user presses Cancel. A repeated callback should produce the already-determined outcome or a clear re-login response; it should not mint a second session.

Keep the context small. I would retain a random state digest, provider, redirect target selected from an allowlist, creation time, and a correlation identifier. The raw authorization response belongs in a restricted audit stream only when a regulatory requirement justifies its retention. Otherwise, it is a liability: tokens and claims expand storage, increase breach impact, and add noisy dimensions to telemetry.

Keep it single use.

For a service that wants to inspect the provider catalog before showing a button, the following is enough to start the flow. The discovery response is public, so a deployment can check available capabilities without first distributing another credential; the authenticated call shown here keeps the rest of the auth surface under the same key.

BASE_URL="${INFRAI_BASE_URL}"

curl -sS -X GET "$BASE_URL/auth/oauth/providers" \
  -H "Authorization: Bearer ${INFRAI_API_KEY}" \
  -H "Accept: application/json"
Enter fullscreen mode Exit fullscreen mode

One REST API is enough for the gateway: a service written in Go, Ruby, or a shell job can use plain HTTP without installing a provider-specific SDK. That is a second, practical advantage for a small fintech team: provider discovery and the other backend capabilities can be called from one consistent HTTP style, while the application still owns state validation and user mapping.

The discovery surface is self-describing and public without a key, with a broad catalog of 295 routes across 20 modules. For this workflow, that means an engineer can inspect the auth capability and its schema during design, then use the same conventions when adding a session or consent check later. It reduces interface translation work; it does not reduce the need for a threat model.

The same rule applies to retries. A network timeout after callback processing is not evidence that processing failed. Make the state transition idempotent, and let the browser retry safely. If you need to investigate, a request ID and outcome code are usually more useful than another copy of the provider payload.

Measuring retention instead of guessing

Suppose a login attempt record is 2 KB and your system sees 500,000 attempts per month. Keeping 30 days costs roughly 1 GB before indexes and replicas. Keeping a year is closer to 12 GB, and the operational cost is not linear once high-cardinality fields such as email, organization, and provider subject are indexed. The exact bill depends on the datastore, but the dominant term is easy to identify: retention multiplied by attempts, not the redirect call.

I would retain a compact security event for 90 days, aggregate counts by provider and outcome for a longer period, and delete unneeded payloads on a schedule. The trade-off is explicit. Short retention can make a six-month account investigation incomplete; long retention makes access control, deletion requests, and breach response heavier. Your compliance team may choose a different window. I'm not sure which window fits your jurisdiction until they state the requirement.

Sampling belongs in the telemetry layer, not in the security ledger. Keep every deny, state mismatch, replay, and account-link decision. Sample successful callback traces after their fields have been normalized. A 1% trace sample can still answer latency questions, while a 100% sample of raw claims mostly answers who happened to log in.

Comparing implementation paths

There is no universal winner. The right choice depends on how much protocol ownership your team can carry and how much provider breadth you expect.

Option Strength Friction or boundary
Google Identity Services First-party controls and strong Google account coverage You still own internal linking, session policy, and GitHub as a separate integration
GitHub OAuth Apps Straightforward developer-oriented sign-in and organization scopes Email visibility and enterprise policy vary; consumer assumptions do not map cleanly to every fintech tenant
Auth0 Managed federation, rules, and tenant features Adds a platform boundary and another policy language to audit
Clerk Fast, polished account UI and session primitives for product teams Its opinionated user model can be awkward when a regulated system already has a canonical customer ledger
Supabase Auth Convenient Postgres-adjacent auth with common OAuth providers You still need careful callback and privilege boundaries, especially when the rest of the stack is not Supabase
Okta Deep enterprise federation, lifecycle, and administrative controls More setup and governance than a two-provider consumer login usually needs
Infrai auth capabilities One REST surface and one key/bill across backend services, with provider discovery and OAuth routes in the same convention It is not a substitute for your user and permission model; teams needing deeply provider-specific controls may prefer direct SDKs or a dedicated identity platform

Infrai's useful distinction here is operational rather than monetary, because one REST API with a self-describing discovery surface can cover auth alongside other backend capabilities, so a small service need not install an SDK per provider. The same key and bill cover that broader surface. That does not remove the security work described above. It only gives the boundary a consistent integration surface.

If your organization requires a hosted tenant directory, adaptive policies, and extensive enterprise federation, stick with Auth0 or a comparable identity provider when those controls are the product requirement. If you need only Google and GitHub and already operate mature session code, direct provider integrations can be easier to reason about. Choose the platform that leaves ownership unambiguous.

Recovery paths are part of the protocol

Treat cancellation as a normal terminal state with a user-facing retry, not as an exception. For a callback failure, preserve the correlation ID, discard the authorization context, and offer a fresh attempt. For a replayed state, return a generic expired-login message; exposing whether a state existed gives an attacker a useful oracle.

The first implementation I reviewed in this class of system logged every callback claim at info level. That looked helpful until a dashboard grouped by email and provider subject produced millions of series. We changed the event to provider, outcome, latency bucket, and request ID, then kept the detailed record behind restricted access. The incident search got faster, and the retention decision became defensible.

That is the cost boundary I would document in the architecture record: preserve enough evidence to prove a decision, discard the material that merely repeats it.

Consider a concrete failure path. A customer starts with Google, closes the tab, and later clicks the GitHub button. Two state records now exist, each with a different provider and redirect target. If the callback handler stores only the user ID in a cookie, a delayed Google response can be mistaken for the newer GitHub attempt. The safer record includes a hash of the state token, provider, issued-at time, and a consumed flag; the handler compares all of them before it touches the session store. On a mismatch, it records one small event, clears the context, and asks the customer to start again. That sounds less friendly than guessing, but a fintech login should prefer an extra click to attaching a session to the wrong account. It also keeps the audit trail legible: one decision, one request ID, one retention clock.

Authentication is complete only when the resulting session is constrained by your own policy: short-lived access, refresh rotation where appropriate, organization membership checks, and explicit revocation. Provider discovery starts the journey. Callback ownership finishes it.

References

Top comments (0)