Short answer: keep user identity global enough to support recovery, but make every application authorization decision tenant-scoped, explicit, and auditable. In an edtech SaaS, a captcha can slow automated signups; it cannot decide which school a user may enter or how that user recovers access.
That boundary is the first design decision. Authentication answers who controls a credential. Authorization answers what that identity may do, for which tenant, and under which application policy. Blurring those questions creates the failures that are hardest to reconcile later: a teacher is authenticated but sees another school's roster, or a recovered email address silently regains an old administrator role.
Keep them separate.
Start With the Tenant Boundary
Treat a tenant as a security context, not as a field that happens to travel in a URL. A request should carry a verified subject identifier, an explicitly selected tenant, and the authorization policy version used for the decision. The server must derive the effective tenant membership from trusted state; accepting a client-provided tenant_id as proof is an access-control bug waiting to happen.
Trust the server.
For signup, the flow can be: create an untrusted registration attempt, verify the captcha, normalize the email, and bind the eventual account to an invitation or a tenant enrollment rule. Do not grant a role merely because the email domain looks familiar. Domains change hands, parents use personal addresses, and a school can have several independently administered applications.
A useful data model separates users, tenants, memberships, and credentials. A membership has a tenant ID, role, status, and timestamps; a credential has recovery and authentication state, but no implicit tenant privilege. This separation lets one person teach in two schools while preserving independent suspension, audit, and recovery decisions.
How Should User Identity and Application Authorization Interact?
The application should authenticate the subject once per session, then authorize each sensitive operation against the selected tenant and current membership. A token claim can accelerate a lookup, but it is not a permanent entitlement: role changes and suspensions need a short cache lifetime or an invalidation mechanism.
I initially thought a tenant claim in an access token would simplify every handler. It did, until the account-recovery path became part of the threat model. A reset link proves control of a recovery channel; it does not prove that the caller should regain a tenant administrator role. Recovery should re-authenticate the user, select the target tenant through a trusted relationship, and re-evaluate membership before issuing a privileged session.
Keep the policy decision close to the operation and record its inputs. The following Go shape makes the tenant check visible without tying the design to a vendor SDK:
type Membership struct {
UserID string
TenantID string
Role string
Active bool
}
func CanEditRoster(m Membership, requestedTenant, subject string) bool {
return m.Active && m.UserID == subject && m.TenantID == requestedTenant &&
(m.Role == "teacher" || m.Role == "admin")
}
The handler should fail closed when the tenant context is absent, malformed, or stale. Return the same externally visible response for an unknown user and a suspended membership where practical; that reduces account enumeration while the audit record still captures the precise internal reason.
Fail closed.
Recovery Is a Separate Authorization Journey
Account recovery deserves its own threat model because it crosses identity proof, tenant selection, and session issuance. OWASP recommends consistent responses and rate controls around authentication and recovery, and the same discipline applies to a multi-tenant reset flow. A captcha protects the signup edge, while recovery needs expiring, single-use tokens, notification of changes, and a review of active sessions.
For an edtech service, support staff may need to restore a teacher's access during a school term. That is an operational exception, not evidence that support should impersonate the teacher. Use a recorded, time-limited elevation with dual approval when policy requires it, and attach the ticket or case identifier to the audit event.
The recovery record should also explain the sequence, not merely its outcome: which factor was reset, which tenant relationship was selected, which sessions were revoked, and who approved any elevation. That detail matters when a parent disputes a roster change months later, because an audit line saying “reset succeeded” cannot distinguish a legitimate teacher from a compromised mailbox; retaining the policy version and correlation ID lets an investigator replay the decision without retaining the secret itself.
The catch is that a single global account can be the wrong choice. It is not suitable when contractual isolation requires separate identities, when tenants control independent identity providers, or when a user must be deleted in one tenant without affecting another legal relationship. In those cases, model tenant-local identities or a carefully defined account-linking process; keep the recovery promise explicit instead of hiding it in a shared email table.
Make Authorization Observable and Reconciliable
An authorization log should answer who requested what, in which tenant, using which policy version, and with what result. Store a correlation ID, subject ID, tenant ID, action, resource identifier, decision, and reason code. Avoid logging reset tokens, captcha answers, or raw personal data. Retention and export rules are compliance constraints, not an afterthought.
Telemetry is useful only when it distinguishes a denied policy decision from a missing membership or an upstream identity failure. Track rates and latency by tenant class, but sample carefully: a high-volume classroom can dominate global charts and hide a smaller tenant's isolation problem. I am not sure one universal retention period fits every jurisdiction; your mileage may vary, so make the period configurable and have counsel map it to the applicable education and privacy obligations.
Reconciliation turns those events into an engineering control. Periodically compare active sessions, memberships, invitations, and recovery events; alert when a suspended membership still has a privileged session or when an invitation is accepted outside its tenant. This is the same exactly-once mindset used in payment ledgers: idempotent state transitions, immutable evidence, and a repair process that can be rerun without inventing a second grant.
A Practical Selection and Rollout Rule
Choose the architecture by recovery and isolation requirements before comparing products. Ask whether the system supports tenant-scoped policy evaluation, independent membership lifecycle, delegated administration, phishing-resistant authentication options, rate limiting, and exportable audit events. Test cross-tenant access with property-based cases: every resource ID should be useless without a matching tenant context, even when the user is valid and the role is broad.
Roll out in stages. First instrument decisions in shadow mode, then enforce read paths, then writes, and finally privileged recovery. Backfill memberships with an explicit provenance field; never infer historical roles from today's domain rules. During migration, make retries idempotent and preserve the old session's tenant context until the new authorization check has succeeded.
A compact decision rule is enough: use a shared identity only when recovery and legal boundaries permit it; require an explicit membership for every tenant action; and make the audit trail capable of explaining both grants and denials. That's it. This keeps captcha in its proper place and prevents an authentication success from becoming an accidental authorization grant.
References
- OWASP Authentication Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
- OWASP Authorization Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html
- NIST Digital Identity Guidelines (SP 800-63B): https://pages.nist.gov/800-63-3/sp800-63b.html
Top comments (0)