Authorization

Authorization: How It Works, Models, and B2B SaaS Architecture

What is authorization?

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:

  • Subject: the person, service or agent making the request
  • Tenant: the customer account in which the request is being made
  • Action: the operation, such as invoice.read or member.remove
  • Resource: the specific record, route or feature being requested
  • Context: facts that can change the decision, such as time, authentication strength or resource state

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 vs. authorization

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.

Question Authentication Authorization
What does it establish? The caller’s identity and authentication state Whether the caller may perform this action on this resource
Typical inputs Credentials, passkey, SSO assertion, MFA result Subject, tenant, action, resource, relationships, attributes and context
Typical output Authenticated identity/session Allow or deny, sometimes with decision metadata
How often does it run? At sign-in and when the session must be renewed or strengthened On every protected request and every protected data path
Common B2B SaaS failure The wrong tenant becomes active after login A valid user accesses another tenant’s object or list result

For a fuller protocol-level comparison, see Authentication vs. Authorization. The API authentication and API authorization guide covers API-specific credential and enforcement choices.

How does authorization work?

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:

  1. The policy administration point (PAP) defines and changes policy.
  2. A policy information point (PIP) supplies facts needed for the decision, such as membership, resource ownership, attributes or relationships.
  3. The policy decision point (PDP) evaluates the request against policy and returns allow or deny.
  4. The policy enforcement point (PEP) blocks or permits the operation.

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:

Authorization request flow showing the PDP returning an allow or deny decision to the PEP for enforcement
The PDP decides; the PEP enforces the returned allow or deny decision.

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.

Authorization methods and access-control models

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.

Model Best fit Watch for
Role-based access control (RBAC) Permissions are assigned to roles, then roles to users or memberships. It fits stable job functions such as tenant admin, billing manager and viewer. Role counts grow when every customer exception becomes a new role.
Attribute-based access control (ABAC) Policies evaluate subject, resource, action and environment attributes. It fits rules involving region, resource state, department, risk or authentication strength. Bad or stale attributes can make policy hard to explain and test.
Relationship-based access control (ReBAC) Policies evaluate relationships between subjects and resources. It fits ownership, teams, folders, nested organizations and sharing. Deep graphs require explicit consistency, lookup and debugging behavior.
Access control lists (ACLs) A resource lists the subjects or groups allowed to access it. It fits direct sharing on a limited number of resources. Per-resource lists become difficult to govern at large scale.
Policy-based or combined model A policy evaluates roles, attributes, relationships and other facts together. It fits B2B products with tenant roles, resource sharing and commercial rules. Precedence becomes dangerous if the team cannot explain which rule won.

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.

The multi-tenant B2B SaaS authorization model

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.

Multi-tenant authorization boundary with Tenant A active, Tenant B inactive, and cross-tenant access denied
Only Tenant A is active; a Tenant B membership does not make Tenant B the request context.

Conceptually, a safe request path should establish at least these conditions before the operation completes:

  1. Trust the authenticated identity. Validate the token or session using the expected issuer, audience, signature and time constraints. The JWT Best Current Practices cover the token-validation rules; the application’s authentication contract must define the expected values.
  2. Resolve the active tenant from a trusted contract. Do not accept an ordinary tenantId request field as proof of membership.
  3. Validate active membership. Confirm that the subject may act in that tenant under the product’s freshness and revocation rules.
  4. Bind the resource to a tenant. Load or address the resource through a tenant-scoped data path.
  5. Evaluate the requested action. Use the roles, permissions, attributes, relationships and commercial state relevant to that tenant and resource.
  6. Enforce the result at the operation. The check must cover reads, writes, lists, exports, bulk operations and background work.

The tenant check cannot be replaced by a role check. admin in Tenant A says nothing about the subject’s rights in Tenant B.

A tenant-aware decision contract

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.

Hierarchies and delegated administration

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.

Where should authorization be enforced?

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.

Placement Best fit Main risk
In-process library or framework filter A small codebase that needs low-overhead checks close to business logic and uses strong shared libraries and tests. Each service can drift or miss a route.
API gateway or middleware Coarse route protection that should run consistently before service-level checks. It may not know the resource or row being accessed.
Local engine or sidecar High-volume services that need governed policy with predictable local decisions. Policy or relationship data can become stale.
Remote authorization service Fine-grained systems that benefit from one governed decision surface and have explicit availability and fallback behavior. Network failure and latency enter the request path.
Data-access layer Tenant scoping, list filtering and defense in depth before rows leave storage. Database-specific policy can diverge from application policy.

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.

List filtering is an authorization problem

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.
Tenant-safe list filtering where tenant and authorization scope constrain the query before authorized results leave storage
One safe list-filtering pattern: tenant and authorization scope constrain the query before rows or metadata leave storage.

There is no single correct way to apply that scope. Choose a retrieval pattern that matches the result size, storage model and freshness requirements:

Pattern Best when
Authorized resource IDs The accessible result universe is bounded and the ID set stays practical to retrieve and apply.
Candidate-page bulk check The application already has a small candidate page and can authorize those candidates before returning it.
Query plan or predicate Large datasets need authorization constraints translated into database-native filtering.
Authorization index Very large inverse lookups justify maintaining a separate projection with an explicit consistency model.

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.

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.

Caching, freshness and failure behavior

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:

Failure Risk range Required decision
Decision service unavailable From hiding a non-sensitive beta feature to exporting customer data. Decide whether a bounded cached result is acceptable. Sensitive actions normally deny without an approved decision.
Relationship data is stale From displaying a cosmetic preference to retaining access after membership removal. Define maximum accepted staleness and how emergency revocation works.
Policy cannot be parsed From disabling an optional control to approving a financial action. Deny by default and alert the policy owner.
Context is missing From omitting optional personalization to not knowing whether step-up authentication is required. Deny the action that depends on the missing context.

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.

Decision records and explainability

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:

  • timestamp and request ID;
  • subject type and stable subject identifier;
  • active tenant identifier;
  • action and resource type/identifier;
  • allow or deny;
  • policy/model version;
  • determining rule or reason code;
  • data or relationship freshness marker;
  • enforcement point;
  • error or fallback state.

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 test matrix

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.

Tenant and resource isolation tests

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.

Test Pass condition and evidence
Cross-tenant object ID A Tenant A member requesting a known Tenant B resource ID is denied without Tenant B data. Retain the decision record and response body.
Forged tenant input Changing tenantId in a request body or URL cannot change the trusted tenant context. Retain the trusted tenant source and denial reason.
Missing membership An authenticated identity without an active membership in the selected tenant is denied. Retain the membership lookup and decision record.
List leakage List, search, count and export endpoints return no Tenant B rows, labels, counts or cursors. Retain response fixtures for every list surface.
Hierarchy escape A parent or sibling administrator is denied outside the explicitly inherited relation. Retain the relationship path and decision reason.

Freshness and lifecycle tests

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.

Test Pass condition and evidence
Role downgrade Removing a permission while an older token or cache entry exists follows the approved freshness contract. Retain before-and-after token, cache and decision times.
Membership removal Removing Tenant A membership follows the verified revocation contract while Tenant B remains separate. Retain token, session and membership evidence.
Expired relationship Temporary access is denied after the approved freshness window. Retain the clock, relationship and decision record.
Stale decision cache Reusing the same subject, action and resource under another tenant cannot produce a cross-tenant cache hit. Retain the cache key and trace.

Operational-path tests

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.

Test Pass condition and evidence
Decision-service outage Each protected action follows its documented fallback rule. Retain the error, fallback state and user response.
Background job Work without an interactive request re-establishes the service or subject and trusted tenant context. Retain the job payload, decision record and affected rows.
Bulk mutation A mixed authorized and unauthorized batch is rejected or processed only under an explicit atomicity contract. Retain per-resource decisions and the final result.

Run the same matrix against UI routes, APIs, scheduled jobs and administrative tools. Attackers look for the one path that skips the check.

What Frontegg documents today

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.

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.

Authorization implementation checklist

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.

  • Define the subject, tenant, action, resource and context contract.
  • Resolve the active tenant from a trusted session or token contract.
  • Validate membership and resource tenant independently.
  • Choose RBAC, ABAC, ReBAC or a combination from real business rules.
  • Name the PAP, PIP, PDP and every PEP in the architecture.
  • Enforce authorization on reads, writes, lists, exports, bulk actions and jobs.
  • Scope data queries before rows leave storage.
  • Define cache keys, maximum staleness and invalidation behavior.
  • Define failure and fallback behavior per protected action.
  • Record enough decision evidence to reconstruct allow and deny results.
  • Test cross-tenant IDs, stale access, missing context, hierarchy escape and outages.
  • Re-run negative tests whenever policy, identity, membership or data access changes.

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.

Looking to take your User Management to the next level?
Sign up. It's free