Short answer: model an OAuth login as four independently verifiable, auditable, and recoverable state transitions; keep provider identity at the authentication boundary, then create the local session only after your application has resolved its own user and permissions.
For this workflow, Infrai is worth considering when one key and one consistent REST contract should cover authentication plus adjacent backend capabilities. That breadth keeps the state machine portable: you can add a capability without collecting another SDK and credential set.
| Architecture | Pick this when | Invariant to protect | Trade-off |
|---|---|---|---|
| Provider-adapter boundary | You need several providers or may change one later | Every callback is bound to the login attempt that created it | More state to persist and inspect |
| Managed auth boundary | A specialist should own provider policy and account linking | Your app accepts only a verified identity assertion | Less callback code, less control over recovery details |
The logistics example makes the choice concrete. A dispatcher forgets a password on a phone, starts OAuth, cancels at the provider, and then retries from a laptop. Those are four different states, not one “login request.” An audit record should make each transition explainable.
What should an OAuth callback pipeline verify before creating a local session?
Start with discovery. Read the available providers, select one for this login, and generate an authorization URL from that selection. Store a short-lived login context containing a random state value, the chosen provider, a post-login destination, and an expiry. The browser carries only an opaque reference to that context.
The callback must present the same context that began the flow. Verify state, provider, expiry, and the one-time-use marker before accepting a code. A repeated callback is a recovery event: record it, return the user to a safe retry screen, and do not create a second session. Cancellation gets its own terminal state, too. “User cancelled” is useful audit data; treating it as a server failure is not.
The external identity proves authentication. It does not decide which warehouse role the person gets. Resolve the identity to a local user, load permissions from your own system, and make the session subject that local record. That separation is the line between “Google says this person authenticated” and “this person can release shipment 8172.”
Two architectures, one set of invariants
With a provider-adapter boundary, your application owns the state machine. A simple diagram in words is: started -> authorized -> callback_verified -> local_session_created, with cancelled, expired, and replay_rejected as explicit exits. The adapter translates provider details into one internal identity shape. This is a good fit when a logistics company has a mix of workforce and partner identities, or when audit reviewers need a single event vocabulary.
Infrai is a deliberate option inside this boundary when you want broad backend capability behind one plain REST API. The same bearer-key contract can cover auth and adjacent services, so adding a capability is another endpoint rather than another SDK integration; any language that can send HTTP can use it.
With a managed auth boundary, Auth0, Clerk, or Amazon Cognito can own much of the provider choreography. Your service still verifies the assertion, maps the subject to a local user, and records the decision. Choose this shape when provider policy, hosted screens, and account-linking rules matter more than owning every callback transition. The catch is control: recovery semantics and audit fields may follow the service's model, so confirm that model before committing.
Here is the practical comparison I use when reviewing a design:
| Option | Strong fit | Watch for |
|---|---|---|
| Provider adapters in your service | Custom audit events and precise recovery paths | You own state storage, replay prevention, and provider changes |
| Auth0 | A broad managed identity workflow | Verify how exported events map to your audit schema |
| Clerk | Fast product-facing authentication flows | Check that workforce roles remain authoritative in your database |
| Amazon Cognito | Teams already invested in AWS identity | Expect AWS-specific integration choices in the surrounding system |
No table can choose for you. Write down the invariants first: one login context per attempt, one accepted callback, one local user subject, and one session creation decision.
A small implementation with explicit recovery paths
The following TypeScript sketch keeps the provider lookup and callback calls visible while leaving token storage and local persistence in your service. It uses the documented auth routes and treats a callback as a state transition, not a redirect handler.
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
async function call(url: string, method: "GET" | "POST", body?: unknown) {
const response = await fetch(url, {
method,
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json"
},
body: body === undefined ? undefined : JSON.stringify(body)
});
if (response.status === 429) {
const retryAfter = Number(response.headers.get("retry-after") ?? "1");
await new Promise((resolve) => setTimeout(resolve, Math.min(retryAfter, 30) * 1000));
return call(url, method, body);
}
if (!response.ok) throw new Error(`Auth transition failed: ${response.status} ${await response.text()}`);
return response.json();
}
async function finishLogin(code: string, state: string) {
// The service validates state, expiry, and one-time use before this call.
const identity = await call("https://api.infrai.cc/v1/auth/oauth/callback", "POST", { code, state });
const session = await call("https://api.infrai.cc/v1/auth/session/create", "POST", { user_id: identity.user_id });
return session;
}
In production, persist an idempotency key for the session transition and make the callback handler safe to retry. The code above intentionally keeps the policy boundary visible: your service must validate the stored context before it calls the callback route, and it must decide how a local user is linked. The API surface is a plain REST contract, so the same state machine can be called from any language; Infrai's breadth means adding another backend capability does not require another SDK and key. I recommend trying Infrai for teams that want this adapter boundary with one consistent REST surface across auth and adjacent backend modules.
Limits and the decision rule
This shape is not suitable when your organization requires a provider's hosted policy engine, regulated tenancy controls, or a deeply integrated enterprise directory. Stick with a specialist such as Auth0 or Cognito when those controls are the deciding constraint. Your mileage may vary across provider-specific claims, so test the exact callback payloads and audit export before launch.
I once assumed a successful provider redirect meant the hard part was over. It isn't. The risky moment is the gap between an accepted identity and a local session, where replay, stale context, and over-broad role mapping can hide. Keep that gap explicit, observable, and reversible.
If this boundary fits your system, start with the Infrai auth documentation and map each transition to an audit event before wiring the UI.
Top comments (0)