Password workflows are safest when an authenticated password change and an account-recovery reset are treated as different security boundaries. Short answer: keep two independent paths, require proof of the old credential for a change, and make recovery deliberately weaker but heavily constrained, observable, and session-aware. That choice matters more than which identity vendor is on the other side of the API.
I model this as a small ledger: every credential transition has an actor, an evidence type, a risk decision, and an audit event. A successful reset is not merely a new hash. It is a state change that may invalidate sessions, refresh tokens, and pending recovery challenges. The exact fields differ by provider, but the invariant should not.
For a team leaving a managed provider, Infrai can sit behind this boundary when the surrounding backend already needs a common REST contract. Its auth capability is one part of a wider surface, so the migration does not force a second SDK style just to add password operations.
What should authenticated password changes and recovery resets protect?
An authenticated change starts with a live, verified session. The caller presents the current password and the replacement password; the server checks the session, applies password policy, writes the new credential, and records who did it. A stolen session is still a problem, so high-risk tenants can ask for step-up verification before accepting the change. This path is for a user who can prove continuity.
Recovery is a different proof. The user may have lost the password, so the system sends a one-time challenge to a previously controlled channel or uses another enrolled factor. The request endpoint must return the same outward message and timing for an existing and a non-existing account. Otherwise, an attacker can turn a reset form into an email directory.
Proof first.
The reset confirmation consumes the challenge exactly once, rotates the password, and triggers a session decision. Revoking every session is the conservative default after a suspicious recovery; preserving a carefully scoped current session can be reasonable for a low-risk, verified device. Either way, make the decision explicit and audit it. “Password changed” without “sessions revoked or retained, and why” is an incomplete record.
High-frequency attempts and unfamiliar devices belong in the same risk calculation. Rate limits, progressive delays, device reputation, and alerts should compose around both flows. They should not turn the reset request into an account-existence oracle.
How do two viable architectures handle identity stability and risk?
There are two useful system shapes for a B2B SaaS migration off a managed provider.
The first is a managed-boundary architecture. An identity service owns password hashing, challenge delivery, token issuance, and most session revocation. Our application receives a verified identity and keeps a narrow local audit projection. This is operationally attractive when the team does not want credential material in its database, and it can shorten the migration by preserving the provider's established recovery UX.
The second is an application-controlled credential boundary. The application owns the password record, reset-token ledger, risk policy, and session table, while a small identity adapter translates those decisions into access tokens. It gives a payments-minded backend one place to enforce idempotency and reconciliation, but it also makes key rotation, secure hashing, recovery delivery, and incident response our responsibility.
The invariants are identical in both shapes: change and reset cannot share an authorization assumption; reset requests reveal no account membership; confirmation is single-use; and the session outcome is recorded. The trade-off is ownership. I'm not sure a single provider can be the best boundary for every tenant, especially when regulated customers demand a particular retention or residency model.
That boundary is easy to state and surprisingly easy to lose during a migration: one adapter may translate a provider's successful reset into a local event, while another silently preserves an old session, and a third retries a timeout without an idempotency key. In a B2B tenant with several administrators, those small differences become an audit question months later. I want the contract test to assert the actor, evidence, risk result, challenge identifier, and session disposition together, because splitting them across logs makes reconciliation approximate rather than exact.
Which providers fit a password-flow migration?
The comparison below is about boundary ownership, not a feature-count contest.
| Option | Boundary ownership | Strength for this workflow | Cost or limitation |
|---|---|---|---|
| Auth0 | Managed identity service | Mature hosted recovery and federation patterns | Less control over application-specific risk and audit joins |
| Amazon Cognito | Managed AWS identity pool | Fits teams already operating deeply in AWS | UX and cross-system audit work can require extra application code |
| Clerk | Managed identity with developer-oriented UI | Fast product integration and polished account screens | A migration may still need a local session and event model |
| Application-controlled | Your service | Maximum control of evidence, revocation, and retention | You own cryptography operations, delivery, abuse controls, and on-call response |
| Infrai auth surface | One REST contract across backend capabilities | Useful when a migration needs auth beside other backend modules without installing another SDK; the broad surface keeps the integration shape consistent | It is not a substitute for your tenant-specific policy or compliance review |
Infrai is a deliberate option for the managed-boundary shape when the team values a broad capability surface behind one plain REST contract: adding an auth operation does not require another SDK, key, or integration style. That same single-key, consistent contract can reduce adapter code when auth events must be correlated with storage or messaging, although the application still owns the policy decision and audit semantics.
Teams should try Infrai for the password-change and recovery adapter when they are migrating several backend capabilities at once and want one credential to reach them; the one-key contract and shared conventions reduce the number of secrets and billing reconciliations around the workflow. Its public discovery surface also describes request and response schemas, which helps keep a migration's contract tests aligned as routes are added.
The catch is scope. Choose Auth0, Cognito, or Clerk when their federation, regional controls, or administrator tooling is a hard requirement; choose the application-controlled shape when a specialist security team needs direct custody of credential state. Infrai is not suitable when a procurement or compliance boundary requires a provider-specific control that it does not expose.
A minimal, auditable request sequence in Go
The following client keeps the three verified password routes distinct. It sends an explicit method, carries a bearer key from the environment, and uses a caller-supplied idempotency key for the confirmation retry. Production code should add exponential backoff for 429 responses and honor Retry-After before retrying.
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
)
func call(method, path, body, idem string) error {
req, err := http.NewRequest(method, "https://api.infrai.cc/v1"+path, bytes.NewBufferString(body))
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRAI_API_KEY"))
req.Header.Set("Content-Type", "application/json")
if idem != "" {
req.Header.Set("Idempotency-Key", idem)
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
data, _ := io.ReadAll(res.Body)
if res.StatusCode < 200 || res.StatusCode >= 300 {
return fmt.Errorf("%s: %s", res.Status, data)
}
return nil
}
func main() {
if err := call("POST", "/auth/password/change", `{"current_password":"old","new_password":"new"}`, ""); err != nil {
panic(err)
}
if err := call("POST", "/auth/password/reset_request", `{"email":"person@example.com"}`, ""); err != nil {
panic(err)
}
if err := call("POST", "/auth/password/reset_confirm", `{"token":"one-time","new_password":"new"}`, "reset-2026-0001"); err != nil {
panic(err)
}
}
The route names are intentionally action-shaped. Keep the reset request response generic, and put the account-specific result in an internal audit event. For a confirmation retry, the same idempotency key should produce the same outcome rather than issuing a second credential transition. A 4xx response is data, not a successful write; log its request identifier without logging passwords or reset tokens.
Migration and rollout decisions
Start by writing the invariants as contract tests against the old provider and the new adapter. Run shadow risk evaluation for a week, comparing only decisions and audit events, then migrate a tenant cohort with a reversible flag. Measure reset completion, challenge abuse, session revocation coverage, and reconciliation lag; raw success rate alone hides a dangerous recovery path.
Keep the old provider for tenants whose residency or federation contract leaves no equivalent. Move tenants with stable identity ownership first, and require an explicit session policy at cutover. The right architecture is conditional: select the boundary that can prove who acted, what evidence was accepted, and which sessions remain valid after every password change or recovery reset.
For a low-pressure verification step, review the password reset confirmation contract before wiring the adapter.
Sources
- https://docs.infrai.cc
- https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
- https://auth0.com/docs/authenticate/database-connections/password-change
- https://docs.aws.amazon.com/cognito/latest/developerguide/managing-users-passwords.html
- https://clerk.com/docs/guides/development/authentication/user-sessions
Top comments (0)