Bypass Mode¶
Bypass mode allows specific operations to read data across tenant boundaries. This is necessary for authentication, admin dashboards, analytics, and other cross-tenant operations.
Admin Bypass¶
The primary bypass mechanism is the admin flag. When rls.is_admin is set to 'true',
the RLS policy allows access to all rows:
from django_rls_tenants import admin_context
with admin_context():
# All rows visible regardless of tenant
all_users = User.objects.all()
This is used by:
admin_context()context managerRLSTenantMiddlewarewhen the user hasis_tenant_admin=Truerls_bypass()test helper (a convenience wrapper aroundadmin_context)
Extra Bypass Flags¶
For edge cases where you need read-only bypass without full admin access, use
extra_bypass_flags on the RLSConstraint:
from django_rls_tenants.rls import RLSConstraint
from django_rls_tenants import RLSProtectedModel
class ProtectedUser(RLSProtectedModel):
# ...
class Meta(RLSProtectedModel.Meta):
constraints = [
RLSConstraint(
field="tenant",
name="%(app_label)s_%(class)s_rls_constraint",
extra_bypass_flags=["rls.auth_bypass"],
),
]
How Extra Bypass Flags Differ from Admin¶
| Behavior | Admin bypass | Extra bypass flags |
|---|---|---|
In CASE WHEN condition (USING clause) |
Yes | Yes |
In CASE WHEN condition (WITH CHECK clause) |
Yes | No |
| Allows reading all rows | Yes | Yes |
| Allows inserting/updating without tenant | Yes | No |
The RLS policy uses a CASE WHEN structure where bypass conditions are checked
first. If any bypass condition is true, the tenant match is skipped entirely.
Extra bypass flags appear only in the USING clause, so they enable read access
(SELECT/UPDATE/DELETE) but not writes without a valid tenant context.
Setting Bypass Flags¶
Use the imperative helpers or context manager:
from django_rls_tenants.tenants.bypass import set_bypass_flag, clear_bypass_flag, bypass_flag
# Context manager (recommended):
with bypass_flag("rls.auth_bypass"):
user = User.objects.get(email="admin@example.com")
# Imperative (for middleware-style code):
set_bypass_flag("rls.auth_bypass")
try:
user = User.objects.get(email="admin@example.com")
finally:
clear_bypass_flag("rls.auth_bypass")
Both imperative helpers accept is_local=True for a transaction-scoped SET LOCAL
flag; this must run inside transaction.atomic(). Pass the same is_local value to
set_bypass_flag() and clear_bypass_flag().
In async code, use async_bypass_flag() (1.4.0+), which has the same save/restore
semantics (see Async Code):
from django_rls_tenants.tenants.bypass import async_bypass_flag
async with async_bypass_flag("rls.auth_bypass"):
user = await User.objects.aget(email="admin@example.com")
Note
The GUC affinity guard follows the admin and tenant GUCs only. A bypass flag stays on the connection it was set on: sibling tasks of the same request share that connection and see the flag while the block runs, and work on another thread does not get it (fail-closed).
Use Case: Authentication¶
During authentication, the user record must be read before the tenant context is known. A bypass flag allows the auth backend to read user records without setting a full admin context.
The backend must cover both credential authentication and get_user(), which Django
uses to reload the user from a session on subsequent requests. Subclassing
ModelBackend preserves Django's password checks, inactive-user rejection, and
permission methods:
from django.contrib.auth.backends import ModelBackend
from django_rls_tenants.tenants.bypass import bypass_flag
class RLSAuthBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
with bypass_flag("rls.auth_bypass"):
return super().authenticate(request, username, password, **kwargs)
def get_user(self, user_id):
with bypass_flag("rls.auth_bypass"):
return super().get_user(user_id)
Configure it in your settings:
The user model's RLSConstraint must include extra_bypass_flags=["rls.auth_bypass"].
This handles user reads only. Authentication can also write to the user table (for
example, password hash upgrades and the last_login update triggered by login()).
Those operations need an explicit tenant or admin context. Group and permission
queries have separate context requirements when the user model inherits
RLSProtectedModel; see
Protecting the User Model.
Security Implications¶
Warning
Bypass modes weaken tenant isolation. Use them only when necessary and for the narrowest scope possible.
Guidelines:
- Prefer
tenant_contextoveradmin_contextwhen you know the tenant. - Use
extra_bypass_flagsinstead ofadmin_contextwhen you only need read access. - Keep bypass blocks small -- enter and exit as quickly as possible.
- Never expose bypass to end users -- bypass should only be used in server-side code that you control.
- Audit bypass usage -- search your codebase for
admin_context,rls_bypass, andbypass_flagto understand your bypass surface area.
When Bypass Is Needed¶
| Scenario | Recommended approach |
|---|---|
| Authentication backends | extra_bypass_flags with bypass_flag() context manager |
| Admin dashboards | admin_context() or is_tenant_admin=True on the user |
| Cross-tenant analytics | admin_context() |
| Data migrations | admin_context() |
| Management commands | admin_context() or tenant_context() depending on scope |
| Background tasks | tenant_context(tenant_id) when tenant is known |