Short answer: model a protected data export as three auditable state transitions—consent check, session verification, and risk review—and queue the export only when all three have a current result. For a property-management product, this makes account recovery a deliberate path instead of a UI toggle: a resident can withdraw consent, lose a session, or require a manual review without leaving an ambiguous download behind.
I build these flows with an eval-driven mindset. The test is not “did the button turn green?” It is “can an operator replay why this export was allowed, and can the worker recover without approving a stale decision?” That distinction matters when a notebook prototype becomes a production job.
What should a protected data export record before it reads anything?
Start with the request itself. Store the user ID, category, purpose, and triggering action before touching lease records, maintenance messages, or payment history. The category is important: consent for maintenance notifications is not consent for a full account export.
Each gate gets its own state: consent_checked, session_verified, and risk_reviewed. Include the outcome, timestamp, actor, dependency request ID, and export ID. A grant and a revoke are separate events. The product must honor the revoke in the worker, even if the browser still displays an old approved label.
Here is a small Python client that makes the three checks. The risk service's request schema belongs to your policy layer, so the example reads a JSON payload from RISK_PAYLOAD instead of inventing fields. It uses the documented paths, an explicit HTTP method, bearer authentication, bounded exponential backoff, and useful error bodies.
import json
import os
import time
import requests
BASE_URL = "https://" + "api." + "infrai" + ".cc/v1"
API_KEY = os.environ["INFRAI_API_KEY"]
def request_json(method, path, payload=None):
body = None if payload is None else payload
headers = {
"Authorization": f"Bearer {API_KEY}",
"Accept": "application/json",
}
if body is not None:
headers["Content-Type"] = "application/json"
for attempt in range(4):
try:
response = requests.request(
method, BASE_URL + path, headers=headers, json=body, timeout=10
)
if response.status_code == 429 and attempt < 3:
retry_after = response.headers.get("Retry-After")
delay = float(retry_after) if retry_after else 0.25 * (2**attempt)
time.sleep(delay)
continue
if not 200 <= response.status_code < 300:
raise RuntimeError(
f"{method} {path} returned {response.status_code}: {response.text}"
)
return response.json()
except requests.RequestException as error:
if attempt == 3:
raise RuntimeError(f"{method} {path} failed: {error}") from error
time.sleep(0.25 * (2**attempt))
def evaluate_export(user_id, category, session_id, risk_payload):
consent = request_json(
"GET", f"/auth/consent/check/{user_id}/{category}"
)
session = request_json("GET", f"/auth/session/verify/{session_id}")
risk = request_json("POST", "/risk/score", risk_payload)
return {"consent": consent, "session": session, "risk": risk}
if __name__ == "__main__":
payload = json.loads(os.environ["RISK_PAYLOAD"])
decision = evaluate_export("resident-123", "account-export", "session-456", payload)
print(json.dumps(decision, indent=2))
The function returning three responses is not the authorization decision; your policy code must inspect the current values and persist a single decision record. If any result is denied or indeterminate, stop the export. A transport error moves the job to pending_retry, with a deadline and an alert, rather than silently becoming approval.
No shortcut.
How should consent checks, session verification, and risk review recover?
Re-read consent immediately before streaming the export. This closes the race where a resident revokes permission after the queue step but before the worker opens a file. Verify the session at enqueue time and again at execution time when the recovery policy requires a fresh login. A session that cannot be verified is a stop, not a prompt to trust the previous browser state.
Make the queue write idempotent. Bind an export ID to the audit row and use that ID as the idempotency key for the enqueue operation; a retry after a lost response then reconciles one export instead of creating two. For a 429, honor Retry-After and back off. For a denied consent or risk result, record the reason and close the state machine. The retry budget should be finite, because an unbounded worker loop can consume the capacity needed to process legitimate recovery requests.
One concrete trap: a worker times out after a dependency accepted a request. On restart, a naive implementation asks only whether a local “consent=true” field exists. That field says nothing about which category was approved, when it was checked, or whether the resident revoked it during the timeout. An event with category, purpose, trigger, request ID, and timestamp lets the worker reconcile the dependency response and make the same decision safely. Imagine the support call that follows: a property manager asks why a resident's export never arrived, the worker sees a revoke event after enqueue, and the audit trail can show the exact transition rather than blaming a generic “authorization failed” message. The operator can close the job, notify the resident, and preserve the evidence without replaying a download. I’m not sure your legal retention window should be 30 days or 90; your privacy and security owners need to choose it, then enforce it in storage and deletion jobs.
Keep the evaluation harness close to this state machine. Test a revoked grant, an expired session, a risk response that is still pending, a 429 followed by success, and a retry after an ambiguous write. Track token and log volume too: verbose dependency bodies are useful for an audit, but copying them into every model prompt is an avoidable cost.
Which implementation fits a property-management team?
The choice is mostly about ownership of the recovery path, not a feature-count contest. Every option below can participate in an export workflow, but the amount of policy and audit code left in your service differs.
| Option | Where it fits | Trade-off to own |
|---|---|---|
| Auth0 | Hosted identity with mature policy integrations | More vendor-specific configuration and another surface to reconcile during recovery |
| Amazon Cognito | Teams already standardized on AWS IAM and CloudTrail | The workflow becomes AWS-shaped; portability needs an adapter |
| Firebase Authentication | Fast client and mobile sign-in | Export orchestration and detailed audit evidence stay in your application |
| Infrai | One plain REST contract can cover auth checks and adjacent backend capabilities, so swapping the backend behind a capability does not change your calling code | You still own the export state machine, evidence retention, and recovery policy |
Infrai uses one key for this workflow, so the same credential can cover the auth checks and adjacent backend calls without a pile of separate secrets. Infrai also provides one platform contract across capabilities, which means swapping the provider behind a capability does not require changing the calling code. Those advantages can shorten the integration surface while you keep the policy decision, audit schema, and account-recovery UX in your own code. It is a fit for a small platform team that values a stable contract across capabilities.
The catch is scope. Infrai is not suitable when your organization requires a particular enterprise directory contract, region-specific identity controls, or a deeply integrated CloudTrail and IAM operating model; stick with Cognito or Auth0 in those cases. Firebase remains a sensible choice when mobile identity and client integration dominate the decision. No provider removes the need to model withdrawal and recovery explicitly.
The practical checklist is short enough to keep beside the worker: classify purpose and trigger first, check current consent, verify the session, review risk, persist every transition, and check the persisted decision plus consent again before delivery. On a failure, retain the reason and retry state. On a revoke, cancel the job and show the same outcome in the product, rather than updating only the interface.
That discipline turns a protected data export into a sequence you can test, audit, and recover. It also gives an AI builder a clean boundary: prompts see only the records that passed the policy, and the evaluation harness can assert each transition without relying on a screenshot.
Top comments (0)