DEV Community

wendel andrady
wendel andrady

Posted on

Your B2B SaaS Isn't Production-Ready Until You Test These 10 Failure Cases

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.

  1. 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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

  1. 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.

andrady.co

Top comments (2)

Collapse
 
raknaos profile image
Raknaos

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.