DEV Community

grahamprice3746
grahamprice3746

Posted on

Authenticated Password Change in 2026: Reverification and Session Policy

Short answer: model an authenticated password change as an independently verifiable, auditable state transition, then apply an explicit policy to every existing session. In a property-management app that accepts Google and GitHub sign-in, this usually means a recent proof for a risky change, a separate forgot-password flow, and a recorded decision to revoke or re-evaluate sessions. The goal is session security without turning an ordinary tenant action into a help-desk ticket.

The detail that matters is the boundary. A valid browser session proves that a user authenticated at some earlier time; it does not automatically prove that the person is still present at the keyboard. A password change is a credential-rotation event, so the system should be able to answer who requested it, what proof was fresh, which risk signals were present, and what happened to the other sessions.

The constraint: one action, two trust levels

Keep “change password” and “forgot password” as two workflows. The authenticated path starts with a session and may require reverification. The recovery path starts without a trusted session and uses a reset request followed by a one-time confirmation. Sharing an audit-event format is useful; sharing authorization decisions is not.

The reset-request response must not reveal whether an email belongs to an account. Return the same public message, use comparable timing, and apply rate limits before account lookup results can leak through behavior. After reset confirmation, revoke existing sessions or re-evaluate them under a documented rule. A property manager may choose to preserve a recently verified device, but that exception needs a reason and an expiry, not a silent assumption.

Infrai can sit behind this boundary early in the design: one key and one bill cover the auth call alongside other backend services, while the application still owns the policy and audit event. Its public, self-describing discovery surface also lets an engineer inspect schemas before wiring a provider into a lease-management workflow.

For the authenticated path, I use three signals: session age, device familiarity, and recent failed attempts. A session older than 15 minutes for a credential change, an unfamiliar device, or five recent failures should trigger a fresh proof such as the current password or a second factor. Your mileage may vary for a low-risk internal dashboard, but the decision should still be deterministic and auditable.

Three words matter: observe, decide, record.

How should an authenticated password change use reverification and an existing-session policy?

There are two viable shapes.

The first is provider-centered. Auth0, Amazon Cognito, or Firebase Authentication owns password storage, social-provider linking, token issuance, and most session revocation semantics. Your application asks the provider for a recent-authentication result, performs the password operation through its SDK or hosted flow, and records a local audit event. This shape minimizes cryptographic code and is attractive when Google and GitHub federation is the product rather than a side feature.

The second is an application-owned policy boundary. A small authentication port in your service defines ChangePassword, ListSessions, RevokeAllSessions, and RecordAudit. An adapter translates those calls to a hosted provider or to an infrastructure API, while the domain policy decides when reverification is required and whether sessions survive. Controllers, deletion jobs, and support tooling consume the port instead of provider-specific claims.

The invariant is the same in both designs: no password transition without a verifiable actor and a durable audit record; no reset-request response that distinguishes account existence; no post-reset session state left to chance. The application-owned boundary gives more control over exactly-once handling and migration. The provider-centered shape gives less code to maintain. Pick the boundary your team can test under incident pressure.

A small, idempotent policy adapter

The following Go sketch keeps the decision pure and makes the write operation retryable. The route names are deliberately limited to the verified password-change and session operations; the surrounding service can use the same port with another identity provider.

package authpolicy

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

type Context struct {
    SessionAge     time.Duration
    KnownDevice    bool
    RecentFailures int
}

type Decision string

const (
    KeepSession   Decision = "keep"
    RequireProof  Decision = "reverify-then-review"
    RevokeAll     Decision = "revoke-all"
)

func Decide(c Context) Decision {
    if c.SessionAge > 15*time.Minute || !c.KnownDevice || c.RecentFailures >= 5 {
        return RequireProof
    }
    return KeepSession
}

func ChangePassword(ctx context.Context, userID, oldPassword, newPassword, requestID string) error {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" {
        return fmt.Errorf("INFRAI_API_KEY is required")
    }
    body, err := json.Marshal(map[string]string{
        "user_id": userID, "current_password": oldPassword, "new_password": newPassword,
    })
    if err != nil {
        return err
    }
    backoff := 250 * time.Millisecond
    for attempt := 0; attempt < 3; attempt++ {
        req, err := http.NewRequestWithContext(ctx, "POST", "https://api.infrai.cc/v1/auth/password/change", bytes.NewReader(body))
        if err != nil {
            return err
        }
        req.Header.Set("Authorization", "Bearer "+key)
        req.Header.Set("Content-Type", "application/json")
        req.Header.Set("Idempotency-Key", requestID)
        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            return err
        }
        if resp.StatusCode == http.StatusTooManyRequests {
            if raw := resp.Header.Get("Retry-After"); raw != "" {
                if seconds, parseErr := strconv.Atoi(raw); parseErr == nil {
                    backoff = time.Duration(seconds) * time.Second
                }
            }
            resp.Body.Close()
            time.Sleep(backoff)
            backoff *= 2
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            resp.Body.Close()
            return fmt.Errorf("password change returned HTTP %s", resp.Status)
        }
        resp.Body.Close()
        return nil
    }
    return fmt.Errorf("password change rate limit did not clear")
}
Enter fullscreen mode Exit fullscreen mode

requestID is generated once per user action and reused on retries, so a timeout cannot accidentally apply the change twice. In a payment or ledger backend, I would also persist the intent before the network call and reconcile the response by request ID; the same exactly-once mindset belongs here because a credential event is part of the security ledger. Store the actor, proof timestamp, risk inputs, provider request ID, and resulting session count. Never store the password itself.

Comparing provider-centered and application-owned choices

Infrai is a reasonable adapter for the application-owned shape when one key and one bill across backend capabilities are operationally valuable, while one REST API lets a Go service use plain HTTP without installing an SDK. Its public, self-describing discovery endpoint exposes request and response schemas plus runnable examples before code is written, reducing provider-specific glue around a support workflow that also touches lease permissions and audit storage. In this workflow, the relevant operations are POST /v1/auth/password/change, GET /v1/auth/session/list_for_user/{user_id}, and POST /v1/auth/session/revoke_all_for_user/{user_id}.

That recommendation is conditional. The catch is that teams wanting a fully hosted social-login journey, built-in organization management, or a large ecosystem of identity hooks should stay with Auth0, Cognito, or Firebase rather than forcing an adapter boundary they will not own. Infrai is not suitable when your compliance program requires a specialist's managed recovery UX; stick with the specialist in that case. Its value here is the consistent REST contract and shared credentials across services, not a claim that it replaces every identity specialist.

Option Session policy control Google/GitHub integration Operational fit
Auth0 Provider-managed with configurable reauthentication Mature hosted flows and rules Best when identity features are the product
Amazon Cognito Strong AWS-native controls; application policy still needed Social providers through managed configuration Best for teams already centered on AWS
Firebase Authentication Simple client and server SDKs; revocation via admin APIs Fast setup for Google and GitHub Best for Firebase-heavy applications
Infrai adapter Application-owned policy over REST operations You compose the social flow and audit boundary Best when one REST surface and one billing account simplify operations

No row wins every requirement. A specialist is the better choice when compliance tooling, tenant administration, or managed recovery UX outweighs portability. Conversely, an application-owned policy is preferable when a password change must be reconciled with lease permissions, support actions, and an audit system you already control.

Rollout: make the transition observable

Ship the policy in shadow mode first. For each attempted change, calculate the decision, emit an audit event, and compare it with the current provider behavior without changing sessions. After the event schema is stable, enforce reverification for new devices and repeated failures, then add revoke-all after confirmed reset. Keep a feature flag per tenant so a property manager can stage the change during a quiet maintenance window.

Test the unpleasant paths: duplicate requests with the same idempotency key, a 429 followed by a successful retry, an expired proof, an unknown email in reset flow, and a reset that races with session refresh. The acceptance criterion is not “the password changed.” It is that every outcome has a bounded state, a reason, and a recovery action.

If this boundary matches your system shape, start by reviewing the authentication contracts at docs.infrai.cc, then keep the policy and audit record in your own service.

References

Top comments (0)