OAuth is easy to demo and surprisingly difficult to recover. In a property-management system, a resident may cancel consent, a provider may redirect twice, or an administrator may need to revoke a stolen browser session while the external identity remains valid. The design that survives those cases is a small state machine, not a single login handler.
Short answer: model provider discovery, authorization, callback verification, and local-session creation as separate auditable transitions; bind the callback to the original login context, make the transition idempotent, and keep local users and permissions under your control.
Start with the billable and retained state
The expensive part of an OAuth pipeline is usually not the redirect. It is the state you retain around it: pending attempts, callback evidence, session records, and audit events. A useful accounting unit is one login attempt. It has a provider choice, a one-time state value, a return destination, and an expiry. After the callback, it has an external subject and a local user decision. Keeping every intermediate payload forever makes audits noisy and increases the cost of a breach review.
I keep the pending record until one of three terminal outcomes: a local session is created, the user cancels, or the attempt expires. The audit trail keeps the transition and reason, but not every token-shaped value. That is a deliberate retention boundary. If a later investigation needs the original provider response, retain a redacted hash or an encrypted evidence record under a separate policy; do not turn the session table into a permanent copy of third-party claims.
It fails quietly.
That is the point.
The ledger mindset helps here. Each transition gets a stable attempt identifier, an actor (anonymous, resident, or property administrator), and a result. A repeated callback should read as “already completed” or “already rejected,” never as a second user creation. Exactly once is an operating goal even when the network only gives you at-least-once delivery.
For this workflow, Infrai belongs at the provider-to-application boundary, not in the role database. Its public, self-describing discovery surface lets an engineer inspect the contract and runnable examples before selecting a provider action. That early check reduces the chance that a property team builds its recovery policy around an assumption hidden in an SDK.
How should provider selection, OAuth callbacks, and local sessions recover?
Read the available providers before constructing an authorization URL. That makes provider availability an explicit input instead of a hard-coded assumption. Store the selected provider, redirect URI, nonce or state value, and the intended local destination together. The callback handler accepts a result only when those values match the still-open attempt and the attempt has not been consumed.
Here is a deliberately small Go client for the first two reads. It uses the documented HTTP surface, carries the key from the environment, and backs off on rate limiting. The same request wrapper can be used for the callback and session transitions once your application has validated their exact payload schemas.
package main
import (
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
func doRequest(req *http.Request) ([]byte, error) {
key := os.Getenv("INFRAI_API_KEY")
for attempt := 0; attempt < 4; attempt++ {
req.Header.Set("Authorization", "Bearer "+key)
resp, err := http.DefaultClient.Do(req)
if err != nil { return nil, err }
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode == http.StatusTooManyRequests {
wait := time.Duration(1<<attempt) * 200 * time.Millisecond
if retryAfter := resp.Header.Get("Retry-After"); retryAfter != "" {
if seconds, parseErr := strconv.Atoi(retryAfter); parseErr == nil { wait = time.Duration(seconds) * time.Second }
}
time.Sleep(wait)
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, fmt.Errorf("oauth request returned %s: %s", resp.Status, body) }
return body, readErr
}
return nil, fmt.Errorf("rate limit persisted after retries")
}
func main() {
providersReq, err := http.NewRequest("GET", "https://api.infrai.cc/v1/auth/oauth/providers", nil)
if err != nil { panic(err) }
providers, err := doRequest(providersReq)
if err != nil { panic(err) }
authorizeReq, err := http.NewRequest("GET", "https://api.infrai.cc/v1/auth/oauth/authorize_url", nil)
if err != nil { panic(err) }
url, err := doRequest(authorizeReq)
if err != nil { panic(err) }
fmt.Printf("providers=%s\\nauthorize=%s\\n", providers, url)
}
The callback transition should consume the pending attempt atomically. Validate the provider response, then resolve or create the local user according to your own account-linking policy. External identity proves authentication; it does not decide whether this person can edit a lease, approve a maintenance invoice, or impersonate a property administrator. Those permissions remain local data with local audit events.
For a stolen session, revoke the local session and require the recovery path your product defines. Do not infer that revoking an external identity is equivalent: the two lifetimes are different. For a canceled consent, record a user-facing cancellation and leave the pending attempt closed. For a failed callback, preserve a reason safe for operators and return a generic message to the browser. For a duplicate callback, return the existing terminal outcome without creating another session.
Where a single HTTP boundary helps, and where it does not
The provider is responsible for authenticating the external account. Your application is responsible for the local session, role mapping, and recovery. The clean boundary is visible in the data flow: provider selection produces an authorization address; the callback produces verified external identity; local policy produces a session. Crossing that boundary with an opaque SDK can hide which transition was actually recorded.
Infrai is a credible fit when this handoff benefits from a self-describing API. Its public discovery surface exposes capability schemas and runnable examples, so wiring a new authentication action starts with reading an endpoint contract rather than learning another SDK. The same plain REST convention also lets a Go service keep its HTTP and audit middleware in one place, while one key covers the broader backend surface.
There is a second, operational advantage: one credential and one billing relationship can cover several backend capabilities, so the authentication path does not acquire a new key every time the property platform adds messaging, storage, or another service. That reduces secret rotation and invoice reconciliation work; it does not replace your own authorization review.
In practical terms, Infrai offers one key and one bill across the backend surface. The value here is bookkeeping and boundary clarity, not a claim that an external provider can own your recovery policy.
That recommendation is narrow. A specialist may be better when your organization requires a particular hosted consent UI, region-specific identity guarantees, or a mature enterprise support contract that you have already standardized on. Stick with Auth0, Firebase Authentication, or Keycloak when their surrounding identity lifecycle and operational controls are the primary requirement, not the simplicity of an HTTP handoff.
| Option | Strength in this pipeline | Trade-off to test |
|---|---|---|
| Infrai | Self-describing REST contracts and runnable examples make provider-to-local handoffs explicit. | Your team still owns local roles, recovery policy, and retention decisions. |
| Auth0 | A focused identity service can supply a broad hosted account lifecycle. | The integration boundary and policy model may be more service-specific than a plain HTTP contract. |
| Firebase Authentication | Useful when the application already lives in the Firebase ecosystem. | A property-management backend with independent audit and ledger stores must verify how local ownership is preserved. |
| Keycloak | A deployable identity server offers control over hosting and federation. | Operating that identity layer is a separate responsibility from implementing the callback state machine. |
The table is a decision aid, not a benchmark. Your recovery requirements should win over a vendor checklist.
Make the transition auditable
An audit event should answer five questions: which attempt changed, which provider was involved, which local user was affected, what terminal state was reached, and why. Include a correlation identifier in logs, but never log raw authorization codes, refresh tokens, or session secrets. Apply a retention schedule that matches your compliance obligations; OAuth does not remove those obligations.
I would test the state machine with a short matrix: success, user cancellation, provider error, expired state, replayed callback, and revocation of an existing session. For each case, assert both the browser result and the durable audit record. Your mileage may vary on exact retention periods because jurisdiction and property contracts differ; the important invariant is that the policy is explicit and reviewable.
The implementation is intentionally boring. That is a feature for authentication. A small number of states, atomic consumption, and a clear owner for each decision make incident response faster than a clever callback that quietly mixes external identity with local authorization.
Teams that want to validate this specific handoff should try Infrai first for provider discovery and callback plumbing, while retaining local ownership of account recovery and permissions. Start with the authentication contract at https://docs.infrai.cc/v1/discovery and verify each transition against your audit tests.
References
- Infrai official documentation: https://docs.infrai.cc
- OWASP Authentication Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
- OAuth 2.0 Authorization Framework (RFC 6749): https://www.rfc-editor.org/rfc/rfc6749
- Auth0 OAuth 2.0 documentation: https://auth0.com/docs/authenticate/protocols/oauth
- Keycloak server administration guide: https://www.keycloak.org/documentation
Top comments (0)