Most B2B SaaS security testing focuses on the happy path.
A user signs in.
They can access their organization's data.
The right role can perform the right action.
Stripe sends a webhook and the subscription updates.
Everything looks correct.
But production failures usually happen somewhere else:
When someone does something they're not supposed to be able to do.
Here are 10 failure cases I think every multi-tenant B2B SaaS should test before production.
- User A can access User B's data
Create two organizations:
Organization A
└── User A
└── Project A
Organization B
└── User B
└── Project B
Then try to access Project B while authenticated as User A.
Don't only test through the UI.
Try:
Direct API requests
Known resource IDs
Modified URLs
Nested resources
Search endpoints
The expected result should always be denial.
A tenant_id column by itself doesn't provide tenant isolation.
- Changing a resource ID bypasses authorization
Suppose your endpoint is:
GET /api/projects/123
What happens if User A changes 123 to 456, where 456 belongs to another organization?
This is a classic place for authorization bugs.
The server needs to verify both:
The resource exists.
The current user is allowed to access it.
Never treat possession of a resource ID as authorization.
- RBAC works in the UI but not the API
Hiding an admin button from a member isn't security.
Try calling the underlying endpoint directly as a member.
For every sensitive action, test:
Owner
Admin
Member
Unauthenticated user
Authorization needs to exist at the server boundary, not just in the frontend.
- A revoked invitation can still be used
Invitations have more states than:
invited → accepted
You should test:
Expired invitation
Revoked invitation
Already-used invitation
Invitation belonging to another organization
An invitation should not become a permanent access token.
- A revoked API key still works
API keys are another authentication path.
Test:
Valid key → allowed
Invalid key → denied
Revoked key → denied
Expired key → denied
Wrong tenant → denied
Also verify that API-key permissions are enforced just like browser-based authentication.
- Usage limits can be bypassed
If your SaaS has usage limits, don't only test:
User reaches limit → request fails.
Try concurrent requests.
For example, if the limit is 100 operations, what happens when 20 requests arrive simultaneously while the tenant has 95 operations remaining?
If the application checks and increments usage incorrectly, several requests may pass before the limit is updated.
Usage enforcement needs to happen server-side and should account for concurrency where relevant.
- The same Stripe webhook produces the effect twice
Webhook providers retry events.
Your application should assume that the same event can arrive more than once.
Test:
Webhook received
Webhook received again
The second delivery should not:
Create a duplicate subscription
Apply a credit twice
Process an upgrade twice
Trigger duplicate provisioning
Webhook processing should be idempotent.
- A background job loses tenant context
Your HTTP request may correctly know:
organization_id = A
But what happens when that work moves into a background job?
The job needs enough trusted context to know which tenant it belongs to.
Test jobs that:
Read tenant data
Modify tenant data
Delete tenant data
Process billing events
Process usage
Background jobs are still part of your application's security boundary.
- A new table doesn't have tenant isolation
This is one of the easiest problems to introduce as a SaaS grows.
You add:
reports
The application works.
Existing tests pass.
But the new table doesn't have the same isolation protections as your other tenant-owned resources.
For every tenant-owned table, verify:
[ ] Ownership relationship exists
[ ] Database policy exists
[ ] Server authorization exists
[ ] Cross-tenant read tested
[ ] Cross-tenant write tested
[ ] Cross-tenant delete tested
- Related resources create an indirect access path
Imagine:
Organization
└── Project
└── Document
You may correctly protect Projects but forget that someone can access a Document directly.
Test the entire chain.
Can User A access a Document belonging to Organization B by knowing:
The document ID?
The project ID?
A nested endpoint?
A search query?
A download URL?
Tenant isolation needs to survive indirect access paths too.
The test that matters most
Don't only ask:
"Does my SaaS work?"
Ask:
"Can I make my SaaS do something I'm not authorized to do?"
That's the test that exposes the gaps.
Free B2B SaaS production checklist
I put the complete production-readiness checklist and a deeper tenant-isolation guide into a free GitHub repository.
production-saas-readiness
It covers authentication, multi-tenancy, RLS, RBAC, invitations, API keys, usage limits, billing, webhooks, background jobs, and production verification.
If you'd rather start with this infrastructure already implemented instead of building it yourself, I've also built a B2B SaaS OS around these components.
Top comments (2)
Point 2 ("never treat possession of a resource ID as authorization") is the one I'd bold. We've seen the pattern where a tenant check exists on the list endpoint but the detail endpoint only filters by id — and no test exercises the seam, so it survives code review and a penetration-free QA cycle at the same time.
One addition that made our own version of this checklist stick: automate the negative tests as first-class CI, not a manual pen-test pass. Each "user A hits user B's resource" case is just a fixture with two accounts and an expected 403/404. Once they're in the pipeline they stop rotting; once they live in a doc titled "security checklist," they do.
Also, "test through the API, not only the UI" — worth extending to the GraphQL introspection / bulk endpoints case if the stack has them. Those often bypass the per-route authorization layer that the REST tests cover one-by-one.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.