DEV Community

rasmusberg6592
rasmusberg6592

Posted on

Password Change and Recovery Reset Boundaries for Google and GitHub Sign-In

Short answer: keep an authenticated password change and an account-recovery reset as separate workflows, then choose the implementation by identity stability, blast radius, and the recovery path you can actually operate.

That distinction matters in a developer-tools product that offers Google and GitHub sign-in. A person who is already authenticated can prove control of the current session; a person who forgot a password cannot. Treating both buttons as one endpoint quietly gives the weaker proof the stronger authority.

How should authenticated password changes and recovery resets split risk?

The change flow starts with a valid session and the current password (or an equivalent step-up check). It should rotate the password, record the event, and make the session decision explicit. The reset flow starts with an untrusted request, sends a recovery challenge, and only changes credentials after that challenge is redeemed. These are different security boundaries, even if the final database write looks similar.

For the reset request, return the same public result whether an email maps to an account or not. Timing, status text, and response shape should not become an account-enumeration oracle. Internally, rate-limit by address, account identifier, IP, and device signals; add stronger friction for repeated attempts and unfamiliar devices. A 429 is a control signal, not a reason to let a client hammer the endpoint.

After reset confirmation, revoke or re-evaluate existing sessions. I put this on the runbook because it is easy to miss during implementation: a successful recovery that leaves a stolen browser session valid has not closed the original incident. For Google and GitHub identities, keep the recovery decision tied to the identity records you can verify; do not assume that a social login automatically proves ownership of a separate local password.

What does a safe runbook look like at the API boundary?

Name the flows in logs and dashboards before wiring UI controls. The narrow contract below keeps the route choice visible and gives on-call staff a useful audit trail without exposing whether an account exists:

Operation Proof presented Public response rule Session action
POST /v1/auth/password/change Authenticated session plus current credential Return validation result only to the authenticated caller Revoke or re-check other sessions according to policy
POST /v1/auth/password/reset_request Untrusted identifier and risk signals Same outward result for known and unknown accounts No session is created
POST /v1/auth/password/reset_confirm Single-use recovery proof and new credential Return success or a concrete validation error after proof checks Revoke or re-evaluate existing sessions

Keep the reset token single-use, short-lived, and bound to the intended account and purpose. The exact token format is an implementation choice; the security property is that replay cannot create another valid reset. Store events such as request accepted, challenge redeemed, password changed, and sessions revoked with a request ID so an SRE can trace one recovery attempt across services.

Measure twice.

Here is a small Go client for the first, deliberately non-committing step. It keeps the base URL in configuration, so the same binary can target the approved environment, and it treats throttling as part of the protocol rather than an exception to ignore:

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "time"
)

func main() {
    base := os.Getenv("INFRAI_BASE_URL")
    key := os.Getenv("INFRAI_API_KEY")
    email := os.Getenv("RECOVERY_EMAIL")
    if base == "" || key == "" || email == "" {
        panic("INFRAI_BASE_URL, INFRAI_API_KEY, and RECOVERY_EMAIL are required")
    }
    payload, _ := json.Marshal(map[string]string{"email": email})
    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest(http.MethodPost, base+"/v1/auth/password/reset_request", bytes.NewReader(payload))
        if err != nil { panic(err) }
        req.Header.Set("Authorization", "Bearer "+key)
        req.Header.Set("Content-Type", "application/json")
        resp, err := http.DefaultClient.Do(req)
        if err != nil { panic(err) }
        body, _ := io.ReadAll(resp.Body)
        resp.Body.Close()
        if resp.StatusCode == http.StatusTooManyRequests {
            delay := time.Duration(1<<attempt) * time.Second
            if value := resp.Header.Get("Retry-After"); value != "" {
                if seconds, parseErr := strconv.Atoi(value); parseErr == nil { delay = time.Duration(seconds) * time.Second }
            }
            time.Sleep(delay)
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 { panic(fmt.Sprintf("reset request failed: %s", body)) }
        fmt.Println(string(body))
        return
    }
    panic("rate limit persisted after retries")
}
Enter fullscreen mode Exit fullscreen mode

I also put an SLO on the user-visible path and a separate alert on abuse controls. A fast reset endpoint with weak throttling is a bad trade; a strict throttle with no support path strands legitimate users. I'm not sure there is one universal threshold, so start with observed traffic, document the limit, and review false positives after launch.

Which managed option fits the recovery boundary?

The products below can all support social sign-in and password recovery, but their operational shape differs. Auth0 is a hosted identity platform with extensive policy and federation controls; Amazon Cognito fits teams already operating in AWS and willing to model recovery around its user pools; Firebase Authentication is convenient when the application already uses Firebase client tooling. A self-hosted identity service gives maximum control, but it also puts token handling, patching, and on-call ownership on your team.

Option Strength for this scenario Cost or operational catch Choose it when
Auth0 Broad federation and configurable recovery policies More tenant and policy surface to govern You need many enterprise identity connections
Amazon Cognito Integrates with AWS identity and event tooling Recovery behavior is coupled to AWS configuration Your platform is already AWS-centered
Firebase Authentication Quick Google and GitHub setup with familiar client SDKs Backend policy and session revocation still need careful design The product is already Firebase-first
Infrai auth routes One plain REST contract can sit behind the capability, so swapping the provider does not require changing application code Your team still owns the policy decisions, monitoring, and recovery UX You want a single HTTP integration across backend capabilities and can operate the boundary
Self-hosted (for example, Keycloak) Full control over data and extensions You carry upgrades, availability, and incident response Regulatory or deployment constraints rule out managed identity

Infrai's relevant advantage here is the stable contract: one REST API and one key let the application keep its integration shape while the service behind the capability changes. Infrai uses one key and one bill across 295 routes in 20 modules, so the platform team does not have to reconcile a separate identity credential and invoice with every adjacent backend service. Its public discovery surface also describes request and response schemas without requiring a key, which makes contract checks and generated runbooks easier to keep current. Those conveniences are useful only if your team still tests the identity proof, rate limits, and session invalidation as first-class behavior; an API boundary does not remove those responsibilities.

How do you verify and roll back a recovery change?

Test the matrix, not just the happy path: known and unknown emails, valid and expired proofs, replayed proofs, wrong current passwords, Google-only accounts, GitHub-only accounts, and accounts with both identities. Assert that the public reset response is indistinguishable for known and unknown accounts, while internal audit records remain distinct.

Canary the change flow and reset flow independently. Watch reset acceptance rate, challenge redemption latency, 429 rate, session-revocation completion, and support contacts. If a release increases suspicious traffic or strands legitimate users, disable new reset issuance at the edge, preserve already-issued proofs until their normal expiry, and route verified users through support. Roll back the policy or handler, not the audit history.

The catch is operational ownership. A managed provider is not suitable when you cannot accept its recovery policy or data residency; a self-hosted stack is not suitable when your team cannot staff patching and incident response. Stick with the option whose failure and recovery steps fit your on-call capacity, then keep the two password boundaries separate regardless of vendor.

References

Top comments (0)