Property managers need Google and GitHub sign-in to feel quick, while a password change must still protect leases, payouts, and tenant records. Short answer: require recent proof of the account owner before changing a password, then invalidate every session except the one that completes the change after it receives a fresh, narrowly scoped session record. This keeps a stolen browser cookie from surviving a credential reset without forcing a legitimate manager through a full login on every routine visit.
I learned to treat this as a ledger problem, not a form problem. In one review, a password endpoint returned 200 while a second device stayed authenticated; the visible symptom was harmless, but the audit trail could not explain which credential state that device represented. We traced the request through the API gateway, account row, refresh-token table, and notification worker, then replayed it with two browser sessions and a delayed database commit. The fix was a policy decision first and code second. A password update is an authentication event with an ordered record, an actor, and a revocation boundary. That ordering matters during reconciliation because support staff need to distinguish a user-initiated change from an administrative reset, and because a retry must not create a second credential version or a second “all sessions revoked” event.
No exceptions.
What should a property-management password change prove?
An already authenticated request proves possession of a session, not necessarily control of the person’s current credentials. Before accepting a new password, ask for a recent factor: the current password, a verified email or authenticator challenge, or a fresh OAuth reauthentication at Google or GitHub. OWASP calls this reauthentication and recommends it for high-risk actions, with a risk-based timeout rather than a single universal number.
The server should record reauthenticated_at and the factor used. Do not trust a timestamp supplied by the browser. A seven-minute window might be reasonable for an internal leasing dashboard, but your mileage may vary when staff share terminals or process regulated payments; make the interval configuration explicit, log its policy version, and test the boundary at exactly seven minutes.
The password itself belongs in a memory-hard password hash, never in the audit event. Store a credential version or password-change sequence on the account. Every session token carries the sequence it saw at issuance, so a mismatch is enough to reject it without trying to infer intent from a token’s age.
How do existing sessions behave after reverification and change?
There are three defensible policies. Revoke all sessions, revoke all sessions except the freshly reauthenticated one, or keep sessions and require step-up authentication for sensitive actions. For property management, the middle policy is usually the practical balance: a manager who just proved control can continue, while a forgotten tablet cannot approve a payout.
| Policy | Security effect | Friction | Suitable boundary |
|---|---|---|---|
| Revoke all | Strongest containment if takeover is suspected | Highest; user logs in again everywhere | Incident response or support-forced reset |
| Keep only the current session | Limits cookie theft and preserves the active workflow | Moderate; other devices prompt again | Normal self-service change |
| Keep all, step up later | Least disruption | Risk moves to every protected action | Low-risk portals with strong per-action checks |
Do not implement revocation by deleting only the browser cookie. Maintain a server-side session record or a revocation epoch, and make refresh-token rotation honor it. The password-change transaction should append an immutable audit event, increment the credential sequence, revoke refresh credentials according to the selected policy, and commit these writes atomically. If the audit append fails, the transaction must fail closed; a successful password response with no corresponding event is an accounting gap.
Here is a compact Go shape for the decision boundary. It deliberately leaves storage and hashing behind interfaces so the same rule can run in a monolith or a service.
package auth
import "context"
type ChangeRequest struct {
AccountID string
CurrentPassword string
NewPassword string
ReauthAge int
SessionID string
}
type Store interface {
ChangePassword(ctx context.Context, accountID, newHash string, keepSession string) error
}
func ChangePassword(ctx context.Context, s Store, req ChangeRequest, hash func(string) (string, error), verify func(string) bool) error {
if req.ReauthAge > 7*60 || req.SessionID == "" {
return ErrRecentProofRequired
}
if len(req.NewPassword) < 14 || !verify(req.NewPassword) {
return ErrPasswordPolicy
}
newHash, err := hash(req.NewPassword)
if err != nil {
return err
}
return s.ChangePassword(ctx, req.AccountID, newHash, req.SessionID)
}
The production store should perform the sequence increment, audit append, and refresh-token revocation in one database transaction. A queue is useful for notifications, but it is not the source of truth for revocation. Return a generic response for wrong current passwords and expired reauthentication, so an attacker cannot use the endpoint as an account probe.
Which implementation trade-offs survive Google and GitHub login?
Social sign-in does not remove the password policy. Some property managers create a local password after joining with Google; others never have one. Model those states explicitly. A user with no local credential should see a set-password flow that requires a fresh provider assertion, while a user who already has a password must pass the local reauthentication rule. Linking a GitHub identity to an existing account also needs recent proof on both sides, or an attacker who controls one browser session can attach a second identity.
Hosted identity products such as Auth0, Okta, and Clerk differ in where session revocation, provider linking, and audit export live. That boundary is the engineering trade-off, not a scorecard: a hosted system can reduce implementation surface, while a self-managed stack gives direct control over transaction ordering and retention. Verify each product’s current hooks and export semantics before committing; I’m not sure any default configuration will match a seven-minute reauthentication rule and a keep-current-session policy without explicit setup.
The catch is operational. This design is not suitable when the business cannot tolerate re-login on shared devices, or when an offline mobile workflow must continue after a credential change. In those cases, keep a narrower capability token for read-only work and require online step-up for lease edits and payments. Stick with full revocation when support reports suspected takeover; convenience is the wrong optimization during containment.
A measured rollout for auditability
Start with shadow logging: record which sessions would be revoked and which reauthentication factor was present, without changing access. Compare those events with support tickets and provider callback logs for at least one billing cycle. Then enforce the sequence check for refresh tokens, followed by the password transaction, and finally the identity-linking path.
Alert on impossible orderings, such as a session using an old credential sequence after a recorded revocation, or two password changes inside one minute from distant networks. Keep event identifiers stable so reconciliation can join database records, provider subject IDs, and notification delivery without guessing. I prefer a boring dashboard with counts and sample IDs over a green “success” percentage that hides missing events.
A final review should answer four questions: what proves recent control, which sessions survive, which records are immutable, and who can override the rule. If those answers are written as policy and exercised in integration tests, the Google/GitHub entry point remains low-friction without turning password change into an untracked escape hatch.
Top comments (0)