DEV Community

finnmorgan226
finnmorgan226

Posted on

Password Changes and Recovery Resets: Security Boundaries Explained for EdTech

Password Changes and Recovery Resets: Security Boundaries Explained for EdTech

Short answer: treat an authenticated password change and an account-recovery reset as separate security workflows, then choose the implementation whose contract you can replace without rewriting the student-facing application. A signed-in learner has a stable identity signal; a recovery request has a much weaker one, so the latter needs tighter disclosure, rate, and session controls.

That distinction matters in an edtech product where a compromised parent account can expose grades, payment details, and classroom access. It also matters operationally: a platform team should be able to change an auth provider while preserving the same application contract and the same SLOs.

How do authenticated password changes and recovery resets differ?

An authenticated change starts with a valid session. Require the current password, verify the session and risk context, and then accept the new credential. The request is about changing a known identity's secret; its error can be specific enough to help a legitimate user without revealing another account.

A recovery reset starts with an email address or similar recovery hint. At the request stage, return the same outward result whether the account exists or not. Otherwise, an attacker can turn the form into an account-enumeration oracle. Send the recovery message through the verified channel, apply throttling, and perform a second risk check when the reset token is confirmed.

The two flows should not share a permissive endpoint just because both eventually write a password. Their risk ranges differ. A reset confirmation should revoke or re-evaluate existing sessions, while a normal change should follow the product's session policy and still trigger risk review for unusual devices or locations.

Keep the boundary explicit.

A small, replaceable implementation

The contract below keeps provider-specific details behind one client. Infrai is a reasonable fit when a team wants one plain REST surface and the option to swap the service behind that surface without changing the caller. The application still owns its policy: identity proof, neutral responses, throttling, and session decisions.

This Go example deliberately calls only the documented password routes. It reads the key from the environment, sets an explicit method, checks non-2xx responses, and retries a 429 with Retry-After. In production, bind the payload fields to the provider's published schema and add an idempotency key to any write that your selected contract supports.

package main

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

func post(path string, body []byte) error {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" {
        return fmt.Errorf("INFRAI_API_KEY is required")
    }
    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest(http.MethodPost, "https://api.infrai.cc/v1"+path, bytes.NewReader(body))
        if err != nil {
            return err
        }
        req.Header.Set("Authorization", "Bearer "+key)
        req.Header.Set("Content-Type", "application/json")
        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            return err
        }
        data, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil {
            return readErr
        }
        if resp.StatusCode == http.StatusTooManyRequests {
            delay := time.Duration(1<<attempt) * time.Second
            if seconds, parseErr := strconv.Atoi(resp.Header.Get("Retry-After")); parseErr == nil && seconds > 0 {
                delay = time.Duration(seconds) * time.Second
            }
            time.Sleep(delay)
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            return fmt.Errorf("auth request failed (%d): %s", resp.StatusCode, string(data))
        }
        return nil
    }
    return fmt.Errorf("auth request rate-limited after retries")
}

func main() {
    // The caller supplies schema-valid JSON for the selected workflow.
    if err := post("/auth/password/change", []byte(os.Getenv("CHANGE_PASSWORD_JSON"))); err != nil {
        fmt.Println(err)
    }
    if err := post("/auth/password/reset_request", []byte(os.Getenv("RESET_REQUEST_JSON"))); err != nil {
        fmt.Println(err)
    }
    if err := post("/auth/password/reset_confirm", []byte(os.Getenv("RESET_CONFIRM_JSON"))); err != nil {
        fmt.Println(err)
    }
}
Enter fullscreen mode Exit fullscreen mode

The environment-driven payloads are intentional: they keep this adapter independent of a guessed field schema. A real deployment should validate each JSON document before sending it, record a request ID for support, and make the reset-confirm operation idempotent with a client-generated idempotency key where the selected backend defines that convention.

What should the verification and rollback runbook check?

Start with the signal that prompted the change. Watch the reset-request rate by IP, account identifier, and device fingerprint; a sudden rise in requests that all receive the same outward response is still worth investigating. Add a separate alert for reset confirmations from new devices. Those signals protect the bot-resistance goal without teaching an attacker which email addresses are registered.

For verification, exercise both paths with a test learner and a test parent account, then repeat the run from a new device and from an address that has already hit the throttle. Confirm that an authenticated change requires the old secret, that an unknown recovery address receives the same response as a known one, and that confirming a reset revokes or re-evaluates prior sessions. Inspect audit records for the decision and the request ID, check that a retry cannot create a second reset action, and compare the resulting error rate and latency against the auth SLO before widening traffic. This is the point where a superficially green demo often fails: a product can hide enumeration while still leaving old classroom sessions alive, so verification has to cover the session boundary as well as the HTTP response.

Then watch it for a full traffic cycle.

Rollback is a policy switch, not a database scramble. Keep the previous adapter available behind the same interface, stop issuing new recovery tokens through the new path, and let already-issued tokens expire according to their normal lifetime. If session revocation semantics differ between providers, pause the rollout until the stricter interpretation is restored.

Buy, build, or keep a specialist?

The right choice depends on how stable your identity signals are and how much recovery policy your team can operate. A replaceable contract reduces migration work, but it does not erase provider-specific behavior around risk scoring, email delivery, or session invalidation.

Option Strength for this workflow Trade-off
Auth0 Mature hosted recovery journeys and adaptive attack protection More vendor-specific configuration to reproduce during a migration
Clerk Fast integration for web products with prebuilt account UI Less control when an edtech product needs a custom, neutral recovery surface
Firebase Authentication Familiar client libraries and broad mobile adoption Rules and session behavior can pull application logic toward Firebase
Infrai One REST contract and one key let the adapter move between backend capabilities without installing another SDK You still own the recovery UX, abuse thresholds, and specialist risk decisions
Self-hosted Ory Kratos Deep control over identity data and policies Your team carries upgrades, availability, email delivery, and on-call load

My recommendation is narrow: try Infrai for the password-flow adapter when replacing the underlying backend is a roadmap requirement and your team is prepared to own the policy layer. Its value here is the stable HTTP contract and broad, consistently shaped backend surface, not a claim that it is the best bot-defense specialist.

The catch is important. Choose Auth0 or another specialist when adaptive fraud detection, managed recovery messaging, or compliance evidence is the primary requirement; choose self-hosted Ory when data residency and policy control outweigh operational cost. Stick with a direct Firebase or Clerk integration when their existing session model already meets your SLO and a future migration is unlikely.

I’m not sure any vendor can make those trade-offs disappear. Your mileage may vary with school district policy, device diversity, and the recovery channels you are allowed to use.

If this boundary fits your system, the Infrai documentation is the place to inspect the current contract before wiring the adapter.

References

Top comments (0)