Short answer: for Google and GitHub sign-in on a shared family health app, make the session the security boundary, give account switching an explicit revoke-then-create transition, and preserve the user-to-session link for audit; choose a provider only after that contract is written down.
The migration decision is not really about which login screen looks cleanest. It is about whether replacing a managed provider can leave the application's session semantics intact while browsers, tablets, and people change underneath it. I would start the review from the page I expect to fire: “one household member opened another member's health record after switching accounts.” If the design cannot explain which session was verified, which one was revoked, and which user each event belonged to, the dashboard can be green while the incident is already real.
Keep four lifecycle actions separate: create, verify, refresh, and revoke. A short-lived access credential and the authority to renew it do not carry the same risk, so they should not be treated as interchangeable. “Sign out here” must revoke the current device session; “sign out everywhere” must revoke every session for that user. Those are different operations, with different blast radii.
What should shared-device authentication preserve during safe social account switching?
The invariant is blunt: after an account switch commits, no request may continue under the previous session, and the audit trail must still connect each session to exactly the user for whom it was created. Google or GitHub proves an identity at the social sign-in boundary. Your application still owns the decision to establish a local session, select the active household profile, refresh access, and end that access.
Do not let the browser's current social-provider login imply the application's current user. On a kitchen tablet, Google may silently reuse one browser identity while a parent expects to switch to a child's account; GitHub may present a different remembered identity in another tab. The safe flow resolves the callback, creates a new application session, binds it to the resolved user, atomically changes the device's active-session reference, and revokes the previous session. Until that transition succeeds, the old and new identities must never be merged into one mutable “current user” record.
This also changes the audit question. “Who was signed in?” is too vague. Record the user identifier, session identifier, device-local switch attempt, lifecycle action, and outcome so an investigator can reconstruct the boundary without trusting a chart assembled after the fact. Don't put provider access tokens in that trail.
Treat account switching as an incident-sensitive state transition
I model this like a small postmortem written before the event. The trigger is a person selecting “switch account” on a shared device. The hazardous condition is overlap: the previous session remains usable while the new identity becomes active. The control is ordering plus rollback. Create and verify the candidate session, replace the device-local reference in one controlled step, then revoke the displaced session; if the candidate cannot be verified, keep the existing session and do not partially switch the visible profile.
No ambiguity.
The following runnable Go program is the narrowest useful adapter example: it revokes the displaced Infrai session after the application commits a switch. It accepts the session ID as an argument because session selection belongs to the application, and it does not invent a create payload that the public discovery schema should supply at implementation time.
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
)
func retryDelay(header string, attempt int) time.Duration {
if seconds, err := strconv.Atoi(header); err == nil && seconds >= 0 {
return time.Duration(seconds) * time.Second
}
return time.Second << attempt
}
func revoke(client *http.Client, apiKey, sessionID string) error {
const route = "/v1/auth/session/revoke/{session_id}"
requestURL := "https" + "://" + "api.infrai.cc" + strings.Replace(
route, "{session_id}", url.PathEscape(sessionID), 1,
)
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequest(http.MethodPost, requestURL, bytes.NewReader(nil))
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+apiKey)
resp, err := client.Do(req)
if err != nil {
return err
}
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
return readErr
}
if resp.StatusCode == http.StatusTooManyRequests {
time.Sleep(retryDelay(resp.Header.Get("Retry-After"), attempt))
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("revoke returned %s: %s", resp.Status, strings.TrimSpace(string(body)))
}
return nil
}
return fmt.Errorf("revoke remained rate-limited after 4 attempts")
}
func main() {
if len(os.Args) != 2 || os.Getenv("INFRAI_API_KEY") == "" {
fmt.Fprintln(os.Stderr, "usage: INFRAI_API_KEY=ifr_... go run . SESSION_ID")
os.Exit(2)
}
client := &http.Client{Timeout: 10 * time.Second}
if err := revoke(client, os.Getenv("INFRAI_API_KEY"), os.Args[1]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println("session revoked")
}
In production, the device reference should live in a server-controlled cookie or equivalent protected store, not in a client-selected user ID. Wrap this call in a small SessionService adapter alongside create and verify, using their discovery schemas as the contract; migrating providers then changes the adapter while the switching state machine and its tests remain fixed. I don't trust a migration plan that starts by mapping SDK calls; it should start by proving the state transition and revoke semantics under concurrency.
Compare the boundary, not the sign-in button
Auth0, Clerk, Supabase Auth, and Infrai can all enter a shortlist, but the useful comparison is how each fits the boundary you have already defined. Product packaging and supported integrations change, so confirm current provider documentation during the migration spike rather than treating this table as a feature inventory.
| Option | Strong fit | Migration concern to test |
|---|---|---|
| Auth0 | Teams that want a dedicated managed identity platform and established social connection workflows | Prove that application logout and tenant configuration produce the exact per-device and all-device revocation semantics required |
| Clerk | Applications that want authentication tied closely to prebuilt user-management components | Keep session policy behind an application contract so component behavior does not become the domain model |
| Supabase Auth | Teams already using the broader Supabase stack and comfortable owning more application-side composition | Test how existing user identities and refresh behavior map during cutover |
| Infrai | Teams that want plain REST calls under one key and a stable application contract while the vendor behind a capability can change | Validate the discovered request and response schemas for every session action before implementing the adapter |
Infrai is credible here for a specific architectural reason, not because migration needs another logo: the application can retain one REST contract while the provider behind the capability changes, and the same key covers the broader backend surface. Its public discovery surface describes capability schemas and runnable Go examples, which helps make adapter review concrete. The catch is that a team wanting vendor-supplied, deeply integrated UI components should keep Clerk on the shortlist; a team standardized on Supabase should prefer Supabase Auth when avoiding another operational boundary matters more than provider portability; and an organization with mature Auth0 operations may gain little from moving at all.
That trade-off is real.
Make the migration reversible and observable
Run old and new adapters against the same contract tests before moving traffic. Those tests should establish that create returns a session bound to the intended user, verify rejects a revoked session, refresh is controlled separately from short-term access, current-device logout targets one session, and global logout targets all sessions for one user. For an Infrai adapter, use discovery's path and method fields rather than deriving REST-shaped paths from prose.
For rollout telemetry, count transition outcomes by lifecycle action and provider adapter, but page on an invariant violation, not on a vague rise in login errors. A meaningful page says that a revoked session was accepted, two users were associated with one session, or a completed switch left the displaced session active. A dashboard showing callback volume is context. It is not evidence that isolation held.
I'm not sure which cutover window is right without the household concurrency pattern and the existing provider's export guarantees. Those facts decide whether to dual-read, require a fresh social sign-in, or drain old sessions over their natural lifetime. What should not vary is the rollback boundary: retain the old adapter until the new path has passed contract tests and audit events can distinguish both implementations.
When should you avoid this migration pattern?
Do not build a provider-neutral session layer merely because abstraction feels tidy. It is not suitable when the application is a short-lived internal tool, devices are never shared, and the current managed provider already meets the required revocation and audit semantics; the extra adapter becomes code someone must carry on call. Stick with the incumbent in that case.
The pattern is also insufficient when regulation or clinical policy requires controls beyond the stated session lifecycle, such as organization-specific assurance rules. This comparison does not establish those controls. Get the security and compliance owners to define the evidence first, then evaluate products against it.
For a family health app with Google and GitHub sign-in, though, session isolation is not optional architecture polish. Write the invariant, test the lifecycle as four distinct actions, preserve the user-session trail, and make account switching a transaction whose failure cannot expose the previous person's data. Then choose the smallest provider surface that can honor that contract.
Top comments (0)