Should organisation access be enforced in application code, database policies or both?

Plan organisation access across application checks, database policies, administrators and jobs. Includes a filled architecture brief and isolation tests.

Saas Development
6 October 2026Updated 06 Oct 202610 min readBukhosi Moyo

Quick Answer

Use application authorization and database grants/RLS together where their execution paths support the intended organisation boundary. Identify the effective role for pages, APIs, views, administrators and workers. Privileged credentials can bypass RLS, so they need explicit server-owned scope and current permission checks. Test intended access as well as cross-organisation denial.

Key Takeaways

  • Combine server authorization with tested grants and RLS for ordinary data paths.
  • Check effective role and execution context for views, functions and privileged credentials.
  • Give administrators and jobs only the authority their approved task needs.
  • Do not assume every worker requires BYPASSRLS or service-role access.
  • Verify positive access, tenant denial, writes, jobs and metadata on each route.

Want the full breakdown? Scroll below.

People reviewing work together at a desk with laptops
On this pageJump to a section
  1. 1Introduction
  2. 2Understanding Access Enforcement Layers
  3. 3Database Row-Level Security (RLS) Explained
  4. 4Application Code Enforcement: Flexibility vs Risk
  5. 5Layered Enforcement: Combining Application and Database Controls
  6. 6Administrator Paths and Background Jobs
  7. 7Practical Access-Enforcement Architecture Brief
  8. 8Worked Example: Organisation-Specific User Profiles
  9. 9Define privileged access without treating bypass as protection
  10. 10Filled hypothetical architecture decision
  11. 11Acceptance tests and recovery
  12. 12Documentation boundaries for this decision
  13. 13Frequently asked questions
  14. 14Conclusion
  15. 15Sources

Share this article

Bukhosi Moyo

Growth Partner

Need help growing your company?

We build SEO-first websites and growth systems for South African businesses.

Get Started

Introduction

When building SaaS applications, deciding where to enforce organisation access, application code, database policies, or both, is a critical architectural choice. This article compares these approaches with a focus on layered enforcement, administrator access paths, and background job operations, highlighting how PostgreSQL's row-level security (RLS) and Supabase's implementation shape practical decisions.

Understanding Access Enforcement Layers

Access enforcement can occur at multiple layers:

  • Application Code: Server-side logic that authorizes identity, organisation and action before querying or modifying data. Frontend controls can improve the workflow but are not a trusted access boundary.
  • Database Policies: Built-in mechanisms like PostgreSQL's RLS that restrict which rows a user can access directly at the data source.

Each layer has strengths and limitations. Application code can implement complex, dynamic rules but risks inconsistencies if not applied uniformly. Database policies provide a consistent, central enforcement but may lack flexibility for nuanced business logic.

Database Row-Level Security (RLS) Explained

PostgreSQL's RLS allows defining policies per table that restrict rows visible or modifiable based on the querying user's role and context. For example, enabling RLS on an accounts table can limit users to only see their own accounts. Policies combine permissively (OR) or restrictively (AND) to refine access.

Supabase extends this by mapping requests to roles like anon, authenticated, and service_role, each with distinct privileges. The service_role bypasses RLS and is intended for trusted server-side operations.

Application Code Enforcement: Flexibility vs Risk

Application-level checks allow embedding organisation access logic alongside business rules, enabling complex scenarios like conditional access based on user session data or external APIs. However, this requires rigorous discipline to avoid missing checks, especially in multi-path access scenarios like APIs, admin interfaces, or background jobs.

Layered Enforcement: Combining Application and Database Controls

A best practice is to combine both layers:

  • Use RLS to enforce baseline data isolation and prevent accidental or malicious cross-organisation data leaks.
  • Use application code to implement nuanced business rules, validate inputs, and handle exceptions.

This proposed architecture supplies an additional boundary only for requests that actually run under its policies. Privileged roles, views or functions may bypass them; layered enforcement is not an isolation guarantee.

Administrator Paths and Background Jobs

Administrators and background jobs often require elevated access:

  • Administrators: Start with least privilege for customer administrators and support operators. Separate exceptional platform-wide operations; do not grant bypass by default.
  • Background Jobs: Prefer scoped roles where feasible. A privileged job needs independent scope validation and auditing.

Managing these paths requires clear policies and monitoring.

Practical Access-Enforcement Architecture Brief

Component Enforcement Location Description Notes
User Data Access Database grants/RLS + server checks Policies constrain ordinary roles; server authorizes organisation and action Verify actual role, views, grants and operation policies
Admin Access Database Role + App Scoped support role; exceptional bypass paths require independent server checks. FORCE RLS affects owners, not superusers/BYPASSRLS roles. Audit separately.
Background Jobs Scoped role or separate privileged server path Prefer least privilege; an exceptional bypass job checks trusted scope independently Verify job identity, organisation, selected IDs and affected records
Public Data Database Grants Public tables or buckets with limited grants and RLS policies if needed. Avoid exposing sensitive data; use public buckets carefully.

How to Use This Brief

  • Define roles and privileges clearly in your database.
  • Implement RLS policies per table for organisation data.
  • Enforce business rules in application code consistently.
  • Separate support and job roles with the least privileges their operations require.
  • Audit and monitor all elevated access.

Worked Example: Organisation-Specific User Profiles

Suppose you have a profiles table where users should only access their own profile.

  1. Enable RLS:
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
  1. Create RLS policy:
CREATE POLICY user_own_profile ON profiles
FOR ALL
TO authenticated
USING (user_id = auth.uid())
WITH CHECK (user_id = auth.uid());
  1. Application code:
  • Authenticate user and include JWT with sub claim.
  • Queries use the authenticated role.
  • Business logic enforces additional constraints (e.g., profile completeness).
  1. Admin role:
  • Define a scoped support role; any exceptional bypass operation needs separately checked scope and audit evidence.
  • Admin UI enforces audit logs and restricts actions.

This illustrative SQL assumes user_id is the authenticated user's UUID. It demonstrates owner-only profiles, not organisation membership. Verify the actual schema, grants and role context; the code has not been run here.

Define privileged access without treating bypass as protection

PostgreSQL documents that superusers and roles with BYPASSRLS always bypass row policies. Table owners normally bypass them too; FORCE ROW LEVEL SECURITY can subject an owner to policies. It does not constrain superusers or BYPASSRLS roles, and it does not create audit logs. See the PostgreSQL row-security guide.

For ordinary support work, define a diagnostic role exposing only required fields and organisations. Record the operator, target, purpose, allowed action and access window. An organisation's customer administrator must not inherit platform-wide privileges.

For exceptional platform repair, define a separate server-side operation. Validate the initiating operator and authorised target before using elevated access. Do not pass a customer-supplied organisation identifier directly into an unrestricted query.

Supabase's RLS documentation warns about privileged bypass and views. Verify the effective role on every deployed path. A successful administrator query does not test customer isolation.

Scope background jobs explicitly

A customer-specific job should carry a server-issued job ID, approved organisation, permitted operation and selection boundary. Validate these when the job executes, not just when a browser queues it.

Prefer a role limited by grants and policies. If a service role bypasses RLS, the job's independent scope checks are its protection; RLS cannot rescue a missing filter. Record selected IDs and affected-row counts. Make unexpected scope visible before a destructive action.

A filter covering every active organisation is not the same as a customer-specific scope. Shared maintenance needs its own operation boundary and recovery procedure.

Filled hypothetical architecture decision

A fictional CRM needs ordinary user access, customer administration, support diagnostics and per-organisation aggregation. The proposed design below is a specification, not an implemented or tested system.

Path Required scope Enforcement Evidence
Customer read/write Active organisation and operation-specific membership Server check plus grants/RLS Caller, target, allow/deny test and affected rows
Customer administrator Its organisation's permitted actions Scoped permissions, not platform bypass Test another organisation's known record
Support diagnostics Approved target and minimal fields Server authorisation plus restricted query/view Operator, reason and access window
Aggregation job Organisation bound to trusted job record Scoped role or independently validated job scope Job ID, selected records and execution outcome
Exceptional repair Approved operation and target records Separate privileged server path Approval, before/after evidence and recovery
Public content Deliberately public rows and fields Reviewed grants and policies Anonymous test and sensitive-field inspection

Identify every route to the same records: API, direct database access where exposed, exports, jobs, support views and file delivery. A policy tested on one route does not automatically cover another.

Acceptance tests and recovery

Create fictional Alpha and Beta data with distinct markers. Establish a working Alpha positive control, then request Beta's known IDs with Alpha's session. Repeat per operation and inspect response bodies and exports.

Case Proposed expected result Investigation if it fails
Allowed Alpha read Correct Alpha rows Establish a healthy positive control
Alpha requests Beta Denied or filtered without protected Beta content Check effective role, grant, policy and server scope
Customer admin requests Beta No platform-wide access Check role inheritance and admin route
Membership removed Matches documented revocation design Test existing JWT, refreshed JWT and current membership
Job scope altered No out-of-scope operation Check trusted job record and execution-time validation
Privileged operation Only approved target action Inspect separate authorisation and audit
Export/view requested No forbidden rows or fields Review view behaviour and field allow-list

A denied SELECT may return no rows rather than a permission error. Other operations may fail differently. Agree the contract per route and inspect content, not only status.

If a test exposes another organisation's records, stop the affected path, retain evidence and repair the missing boundary. Do not routinely disable RLS to restore access. Use controlled maintenance access, retest the failed case and positive control, and reconcile affected data.

OWASP's authorisation guidance supports deny-by-default permissions and checks on each request. Logging helps investigate enforcement; it does not replace it.

Documentation boundaries for this decision

PostgreSQL's row-level security (RLS) enforces access control directly in the database by allowing policies that restrict which rows a user can query or modify. These policies are table-specific and can be combined permissively or restrictively. Importantly, superusers and roles with BYPASSRLS privileges can bypass these policies, and table owners typically bypass RLS unless forced. This means that while RLS provides a strong baseline for data isolation, it does not automatically cover all access paths, especially for administrators or special roles. Source: PostgreSQL row security

Supabase enhances PostgreSQL RLS by mapping requests to distinct roles such as anon, authenticated, and service_role. The service_role bypasses RLS and is intended for trusted server-side operations like background jobs. Supabase emphasizes that policies must be defined per role and operation, and that grants must be set correctly to avoid unintended access. Additionally, JWT claims used for authorization may be stale until refreshed, so application logic must not rely solely on user-editable metadata. This highlights the need for combined enforcement in application code and database policies. Source: Supabase row-level security

OWASP recommends a deny-by-default access control approach with explicit permission validation on every request. It warns that relying solely on application-level authorization without enforcing least privilege principles can lead to broken access control, a common and severe vulnerability. Authorization logic should be consistent and cover all access paths, including APIs, admin interfaces, and background jobs. This aligns with layered enforcement where database policies provide baseline isolation and application code handles complex business rules and exceptions. Source: OWASP authorization guidance

Frequently asked questions

Should I rely solely on database policies for access control?

No. While RLS provides strong data isolation, complex business rules and user context often require application-level checks for full enforcement.

How do I handle access for background jobs?

Prefer scoped roles. If bypass is necessary, independently validate trusted job scope, audit the operation and verify affected records.

Can administrators bypass row-level security?

Some platform roles can: superusers and BYPASSRLS roles bypass policies, and table owners normally do too unless forced. A customer's administrator should remain scoped to its organisation. Audit and separately authorize exceptional platform operations.

What about public data access?

Public data can be exposed via database grants or public buckets but ensure sensitive data is never exposed and consider RLS policies even for public data if needed.

Conclusion

Enforcing organisation access requires a layered approach combining database row-level security and application code checks. Verify the chosen boundaries across user, administrator and job contexts; the design alone does not establish isolation. Clear role definitions, policy management, and auditing are essential to maintain robust security.

If your business needs expert guidance on SaaS development or access control architecture, consider consulting Symaxx's SaaS development services. For practical implementation, our platform comparison guide and website maintenance cost overview can help you plan effectively. Understanding the user journey is also vital for designing secure access flows.

If you need help implementing secure access control in your SaaS product, get in touch with Symaxx via our SaaS development route.

Sources

Share this article

Bukhosi Moyo

Written by

Bukhosi Moyo

CEO & Founder

Bukhosi is the founder and lead SEO strategist at Symaxx. He architects search-first digital systems for South African businesses, combining technical engineering with commercial strategy to build long-term organic assets.

Feedback

Was this helpful?

Tell us how this article felt in one click.

Back to Insights

Need help executing this strategy?

Our team turns these insights into revenue-generating search architectures for your business.