Blog post
Request Context Is Where SaaS Trust Becomes Executable
Why request context is not backend plumbing, but the place where identity, tenant scope, and permissions become enforceable.
Request Context Is Where SaaS Trust Becomes Executable
I used to think of request context as backend plumbing.
Useful, necessary, but not especially interesting. A place to put the current user, maybe a request ID, maybe some metadata for logging.
Then I started thinking more seriously about multi-tenant SaaS architecture, and the object changed meaning.
Request context is not just a convenience. It is where the backend turns claims into trust.
The problem it solves
Every meaningful operation needs to answer three questions:
- Who is acting?
- Which workspace or tenant are they acting within?
- What are they allowed to do?
If those answers are rediscovered in every route, action, repository, or component boundary, the system becomes fragile.
One endpoint reads a tenant from the URL.
Another trusts a value from the request body.
A third checks permissions in a helper.
A fourth assumes the UI would never send an invalid workspace.
Individually, each decision can look harmless. Together, they create a system where trust is renegotiated everywhere.
That is not a safe architecture.
Claims are not context
A frontend can express intent.
It can say: the user selected this workspace. The user is opening this record. The user clicked this action.
But those are claims.
They are not yet trusted context.
The backend has to validate them against the authenticated identity, membership, permissions, tenant access, and current policy. Only then can the request operate inside a trusted scope.
That distinction matters.
A selected workspace is not the same thing as an authorized tenant scope.
A role stored somewhere is not the same thing as permission to perform this action now.
A user ID in a session is not enough unless the system also knows what that user can access in this request.
Request context is the object that should carry the validated answer.
What belongs in it
A simple request context might contain:
type RequestContext = {
actorId: string;
tenantScope: TenantScope;
permissions: PermissionSet;
requestId: string;
};
The exact shape does not matter as much as the principle.
The context should be established once, server-side, at the boundary of the request.
It should be passed through the application deliberately.
It should not be casually rebuilt, widened, or overridden by lower layers.
Most importantly, dangerous operations should require it.
If a use case can mutate tenant-owned data without trusted context, the type system is allowing an unsafe state.
Why this matters more with AI-generated code
AI coding agents are very good at continuing local patterns.
If one route passes tenantId manually, another route may do the same.
If one repository accepts raw tenant strings, another repository may copy that shape.
If authorization is optional in one path, generated code may treat it as optional elsewhere.
This is why request context is not only a runtime concept. It is also a design signal.
It tells both humans and agents: identity and tenant scope are not casual parameters. They are trusted inputs created by a controlled boundary.
The clearer that contract is, the less each new piece of code has to invent.
The anti-pattern
The dangerous version looks like this:
await updateRecord({
tenantId: body.tenantId,
userId: session.user.id,
recordId: body.recordId,
data: body.data,
});
It works, but the trust story is unclear.
Where did the tenant come from?
Was it validated against the session?
Can this user act in that tenant?
Is this permission checked here, upstream, or nowhere?
The safer version moves that uncertainty out of the operation:
await updateRecord({
context,
recordId,
data,
});
Now the use case can require a trusted context instead of raw claims.
That does not solve everything, but it changes the default. The operation starts from validated authority, not from values supplied by the request.
The principle
Request context is the backbone because it prevents every part of the backend from inventing its own idea of trust.
It does not make security automatic. Nothing does.
But it gives the system a place where identity, tenant scope, permissions, and traceability become explicit and enforceable.
Without trusted context, every endpoint becomes a place where trust can be accidentally renegotiated.
With it, trust has a path through the system.
Continue exploring
Follow the same line of thought through themes, tags, or a broader local search across the archive.
Keep following the thread.
Working Is Not a Quality Metric
Why producing working code faster with AI raises the stakes on architectural quality, and why passing tests is not the same as having sound boundaries.
Tenant Is Trust, Not Data
Why tenant scope cannot be treated as a field that flows through the system, and what it means to establish it as a trust boundary instead.
When a SaaS Stops Being About Features
Why building a real multi-tenant SaaS stops being a feature problem and becomes a problem of trust boundaries, guarantees, and responsibility placement.