Agen.co enables organizations to securely expose enterprise context to internal agents, copilots, and AI workflows through an identity-aware control layer that governs access, reduces risk, and centralizes oversight.
A low-code CIAM platform for managing customer identity as you scale.
Empower your workforce with secure agents
Authorization is the process of deciding whether a subject may perform an action on a resource under the current conditions. In a multi-tenant application, the decision must also establish the customer account in which the subject is acting and bind the requested resource to that tenant. A useful authorization request names five inputs:
invoice.read
member.remove
The result is normally allow or deny. A production system may also return the policy or rule that determined the result so engineers can explain and investigate it.
In a single-tenant example, “Can user 42 edit document 7?” might be enough. In B2B SaaS, it is incomplete. The same user can belong to several customer accounts, hold a different role in each one and access resources that look identical except for their tenant ownership. The safer question is: “Can user 42, acting in Tenant A, edit document 7, which belongs to Tenant A, under the current policy?”
OWASP defines authorization as verifying that a requested action or service is approved for an entity. The tenant and resource checks are how a SaaS application applies that definition without crossing customer boundaries.
Authentication establishes the caller’s identity and authentication state. Authorization evaluates whether that caller may perform a specific action on a specific resource within the current tenant and context. Authentication usually happens at sign-in or session renewal; authorization must run at every protected route, operation and data path.
An identity provider may issue a signed token after login. The application still has to verify the token, establish the active tenant, identify the requested resource and enforce the relevant policy. A valid token does not prove that its holder may read any record whose identifier they can guess.
For a fuller protocol-level comparison, see Authentication vs. Authorization. The API authentication and API authorization guide covers API-specific credential and enforcement choices.
Authorization works by collecting trusted facts about the subject, tenant, action, resource and context, evaluating those facts against policy, and enforcing the resulting decision before the protected operation completes. Four functions describe that path even when one application process performs all of them:
NIST documents these functions in SP 800-162. They do not have to be separate products or network services. A small application might keep them in one process. A larger platform might manage policy centrally, evaluate it in a local engine and enforce it in an API gateway plus each data service.
The request flow looks like this:
The application must enforce the result at the point where the protected operation occurs. Hiding a button in the browser improves the interface, but it does not protect the backend route or the underlying data.
RBAC, ABAC, ReBAC, ACLs and policy-based combinations answer different kinds of rules. RBAC fits stable job functions, ABAC adds conditions, ReBAC captures ownership and sharing, and ACLs attach grants to a resource. Multi-tenant SaaS systems often combine them, but every combination needs explicit precedence and testable denial behavior.
Traditional access-control taxonomies also include discretionary access control (DAC), where an authorized owner can control access to an object, and mandatory access control (MAC), where a central authority enforces policy using classifications or security attributes. Both remain important security models, but customer-facing B2B SaaS authorization is usually expressed through roles, attributes, relationships, permissions and resource-level policy.
RBAC often supplies the baseline: a member’s tenant-scoped role grants a set of actions. ABAC adds conditions. ReBAC expresses ownership, nesting and sharing. A combined model can answer a rule such as:
Allow invoice.read when: the subject is an active member of the invoice's tenant AND the subject has billing.read in that tenant AND the invoice is not under a legal hold that blocks this operation
See RBAC, ABAC and RBAC vs. ABAC for deeper treatment of each model. The broader RBAC vs. ABAC vs. PBAC comparison covers policy-based combinations. The RBAC best-practices guide owns role lifecycle and governance. For subscription and feature eligibility, use the separate entitlement-management architecture.
A tenant-safe authorization system keeps identity, tenant membership, active tenant context and resource ownership separate. The same person may be an administrator in one customer account and a viewer in another. The decision must bind the current membership and requested resource to the same trusted tenant context before evaluating roles, attributes or relationships.
Consider a user who belongs to Tenant A as an admin and Tenant B as a viewer. The global identity answers who the person is. Each membership answers what role and account-specific state the person has. The active tenant says which membership applies to the current session or request. The resource still needs its own tenant binding.
Conceptually, a safe request path should establish at least these conditions before the operation completes:
tenantId
The tenant check cannot be replaced by a role check. admin in Tenant A says nothing about the subject’s rights in Tenant B.
admin
A tenant-aware decision contract treats tenant context as a server-established input, not caller-controlled metadata. It verifies active membership, loads the resource through tenant scope and then evaluates the action. The pseudocode below shows that boundary without assuming a particular vendor, token library, storage engine or policy language.
This TypeScript is pseudocode. The exact session and policy APIs depend on the application and authorization system.
type AuthorizationInput = { subjectId: string; activeTenantId: string; // established from a trusted server-side contract action: string; resourceType: string; resourceId: string; context: { requestId: string; authenticatedAt?: string; }; }; async function authorize(input: AuthorizationInput): Promise<boolean> { const membership = await memberships.findActive( input.subjectId, input.activeTenantId, ); if (!membership) return false; const resource = await resources.findForTenant( input.activeTenantId, input.resourceType, input.resourceId, ); if (!resource) return false; return policy.check({ subject: { id: input.subjectId, roles: membership.roles }, action: input.action, resource, context: input.context, }); }
The caller supplies a resource type and identifier, but the server loads that resource through the established tenant scope. It does not trust a caller-supplied resource tenant. The policy still decides whether the active member may perform the requested action on the scoped resource.
Parent-child accounts create a second boundary. A parent administrator may manage selected child accounts, but “parent” should not mean unlimited access to every descendant and resource. Model the exact inherited relation or delegated permission, then test the point where inheritance stops.
Keep provider administration separate from customer administration. A SaaS vendor may define which permissions a customer can delegate without letting the customer create a role that crosses a provider-owned boundary. For Frontegg-specific hierarchy behavior, see Account Hierarchy for Multi-Tenant SaaS.
Authorization must be enforced where the protected operation can still be stopped. A gateway can reject a coarse route, but the service or data layer usually has the resource context needed for a final decision. Larger platforms often use several policy enforcement points while keeping one governed decision contract and policy model.
OWASP recommends checking permissions on every request and placing checks where they cannot be bypassed. That usually means coarse checks at the edge and resource-aware enforcement inside the service that performs the operation.
An object check answers whether a subject can read one known resource. A list endpoint asks which resources the subject may see. Fetching an unbounded cross-tenant result and removing unauthorized rows afterward is both risky and expensive because rows, counts, facets or cursors may disclose resources that should never have entered the result set.
A safer list path applies tenant and authorization scope before returning rows:
1. Establish the trusted subject and active tenant. 2. Ask the authorization system for accessible resource IDs or an authorized scope. 3. Query storage with `tenant_id = active_tenant` and the authorized resource scope. 4. Apply a final object check before sensitive mutation or export. 5. Keep pagination, totals, facets and cursors inside the same authorized scope.
There is no single correct way to apply that scope. Choose a retrieval pattern that matches the result size, storage model and freshness requirements:
Relationship-based systems often expose lookup operations for this task. Google describes an authorization-aware search index as a major use case in the Zanzibar paper. Frontegg’s current ReBAC documentation shows cursor-based lookupTargetEntities() pagination with an optional limit that defaults to 50 and is capped at 1,000. Production consistency, total semantics and cross-SDK parity still require verification before the operation becomes part of the reference implementation.
lookupTargetEntities()
Some products deliberately support cross-tenant collaboration. In those systems, the storage query should use the explicitly authorized tenant/resource set rather than a single tenant equality check. The exception belongs in policy and tests; it should not arise from fetching globally and filtering afterward.
List tests should inspect rows, pagination cursors, counts, exports and search suggestions. A zero-row leak can still reveal another tenant through a total count or an autocomplete label.
Authorization inputs change at different speeds. A role might change once a month; a membership can be removed during an incident; a temporary relationship can expire at a precise time. A cache is safe only when its key, lifetime and invalidation rules match the risk of stale access.
A decision cache must account for every input that can change the outcome, either in the cache key or through a reliable versioning or invalidation mechanism. An illustrative key is:
subject + active tenant + action + resource + policy version + relevant context
Leaving the tenant out of the key can reuse an allow decision from Tenant A inside Tenant B. Leaving the resource out can turn one allowed invoice into access to every invoice.
Define failure behavior per operation:
OWASP recommends deny by default and safe failure. Availability requirements still need an explicit design: a team should know which operations deny, which may use a bounded cached result and which degrade to a smaller read-only capability.
An authorization decision record should explain why an operation was allowed or denied without storing credentials or sensitive resource contents. It should identify the decision inputs, policy version, enforcement point and failure state so engineers can reproduce the result during debugging, incident response and policy regression testing. A useful record contains:
These records support debugging, incident response and regression analysis. They are not automatically a compliance audit trail. Retention, access controls, redaction and export behavior need their own design.
Authorization testing must cover allowed operations and deliberate attempts to cross a boundary. Happy-path tests show that valid users can work. Negative tests prove that tenant, resource, hierarchy and failure boundaries still hold when identifiers are forged, membership or policy data is stale, dependencies fail, or request context is incomplete.
Isolation tests attempt to cross the tenant and resource boundary with otherwise valid identities and identifiers. They should prove that a forged tenant value, missing membership, unrelated object ID, hierarchy escape or broad list query cannot expose another customer’s records or metadata. Retain both the denial decision and the protected response.
Freshness tests measure how role, membership and relationship changes affect decisions that depend on older tokens, caches or replicated data. The expected result must follow an approved freshness contract rather than an assumed immediate update. Retain timestamps and before-and-after evidence so the team can measure the actual exposure window.
Operational tests cover paths that often bypass the interactive request flow: dependency outages, scheduled work and bulk mutations. Each path must re-establish trusted context, apply its documented fallback or atomicity rule, and retain enough evidence to reconstruct the decision. A passing API route does not prove these paths are protected.
Run the same matrix against UI routes, APIs, scheduled jobs and administrative tools. Attackers look for the one path that skips the check.
Frontegg’s public documentation uses account and tenant as equivalent terms. Its current JWT documentation identifies tenantId as a required claim and derives the documented roles and permissions arrays from the user’s tenant context. The client-SDK account-switch guide says switching the active tenant refreshes user state and obtains a new JWT.
roles
permissions
That establishes the documented client-SDK token path. The application must still validate the token under its authentication contract and bind the requested resource or data query to the established tenant context. The public claim documentation does not prove that application-resource ownership is enforced automatically.
That token-based path needs a freshness decision. The current Roles documentation says role changes take effect when the current token expires and a new token is issued. Applications should choose token lifetimes and any additional server-side checks according to the cost of stale access.
For resource-level decisions, Frontegg’s ReBAC documentation models entity types, relations, actions and inherited access. The current Node entitlements SDK guide documents feature, permission, route and entity checks plus lookup operations. Deployment, synchronization, fallback, monitoring and logging details belong in the implementation documentation rather than being reproduced here.
The Authorization + Entitlements product page is the commercial route for teams evaluating the Frontegg capability. This guide remains the architecture owner and should link to product evidence without repeating its marketing claims.
An implementation is ready for review when the team can identify every decision input, enforcement point, freshness boundary and denial test. The checklist should be applied to individual reads, lists, exports, bulk operations and background jobs rather than only to interactive API routes.
An authorization system is ready when the team can explain one decision, deny a cross-tenant request on every path and reproduce that denial in a test.