← All posts

· ShipRust · Updated

Rust SaaS multi-tenancy: choosing a starter

Evaluate a Rust SaaS starter for per-user or team accounts with an isolation checklist covering authorization, Postgres, jobs and billing.

For a multi-tenant Rust SaaS, choose the ownership model before choosing a starter. Separate accounts can be enough for a personal productivity app. A product sold to teams also needs shared resources, membership changes and workspace-level permissions. Authentication and a Stripe integration do not establish those rules.

Use the checklist below to decide what must already exist and what you are prepared to implement. It covers an Axum application's browser routes, API, background workers and database, with an evidence worksheet you can reuse when evaluating a starter or reviewing an AI-generated change.

We build ShipRust. Its current generated application supplies account-oriented building blocks, not a ready-made team-workspace system. Product details and linked sources were checked November 5, 2026. This is a design and purchase-evaluation framework, not a claim that we have implemented or penetration-tested the hypothetical team application described here.

In this guide

Choose the customer boundary

A tenant is the customer boundary whose resources must be isolated. That boundary is not necessarily one login. Write down which of these products you are building:

Product modelResource ownerPermission question
Personal accountOne userDoes this authenticated user own this resource?
Shared team workspaceAn organization or workspaceIs this user still a member, and does their role allow this action?
Agency with several client accountsA selected client workspaceWhich client is active, and is this operator permitted to act for it?

For a personal app, a user-owned data model may be the right tenant model. Do not add organizations solely because your product is called SaaS. Conversely, putting a company name on a profile does not create shared ownership.

Consider a proposed reporting product: Alice owns a workspace, Bob is an analyst, and Carol belongs to a different customer. Bob can read reports but cannot invite members. If Alice leaves, the workspace must survive under another owner. Those requirements imply a membership model separate from the users table and a transfer rule separate from deleting Alice's account.

Before buying, describe that journey in plain language. Require the candidate implementation to identify where each rule lives. A starter that leaves this to you can still be useful; price the missing domain work rather than treating a dashboard screenshot as evidence that it exists.

Trace one permission across the application

Follow Bob's request to download a report. The server needs an authenticated identity, a selected workspace, a current membership decision and an action-specific permission. It must also establish that the requested report belongs to that workspace.

A workspace ID in a URL or header identifies the requested context; it is not proof of membership. The OWASP multi-tenant security guidance recommends validated tenant context and tenant-aware checks across data access and asynchronous work. Use that as a review boundary, not as a reason to trust a particular middleware package.

In Axum, a custom extractor can represent validated workspace context. Its value comes from the checks it performs, not from the presence of a Rust type. If it copies an untrusted header into a struct without consulting membership, downstream handlers receive a neatly typed mistake.

Check the same decision through every interface you intend to expose. A browser session, bearer API key and MCP tool call should not acquire different ownership rules by accident. If Bob's key remains valid after he leaves the workspace, decide whether it now loses workspace access, is revoked entirely, or has an explicitly separate service identity. Document and test the policy.

For queued exports, decide when authorization is evaluated. An export accepted before Bob is removed might be canceled, or it might finish for the workspace without being delivered to Bob. Either is a product decision. A worker cannot safely invent it from a user ID captured hours earlier. Carry enough context to enforce your chosen rule when executing and delivering the job.

Choose a Postgres isolation approach

For a shared-database design, compare application-scoped queries with PostgreSQL row-level security. Separate databases are another option when customer-specific restore, operational isolation or deployment requirements justify additional provisioning work. None of these choices supplies invitations, roles or ownership transfer by itself.

Application-scoped queries put the boundary in the application's data-access functions. For example, fetching a report requires both its ID and the validated workspace ID. Review list, search, update and delete paths as well as the detail page. A scoped lookup is not enough if a second export query ignores the boundary.

Row-level security (RLS) adds database policies controlling access to rows. PostgreSQL distinguishes visibility rules from checks on proposed writes. Its row-security documentation also explains an important testing caveat: superusers and roles with BYPASSRLS bypass policies, and table owners normally do too unless forced through RLS. Test using the role your application will actually use. A successful test under an elevated migration role can prove the wrong thing.

If you pass tenant context through a database setting, bind it to the transaction doing the work. PostgreSQL's SET LOCAL reference states that local settings end at commit or rollback. Ensure context assignment and all protected queries use the same transaction and connection; separate pool operations do not establish that relationship.

PGSandbox's RLS testing guide is a useful worked companion: it pairs known rows from two tenants with permitted and denied operations, checks the execution role, and verifies context reset. Its database harness does not establish that your HTTP handlers validate membership. Keep that application test separate.

Choose the approach your team can inspect and maintain. RLS is not a substitute for application permissions, and application filtering is not automatically incorrect. The purchase question is which boundary is implemented, which evidence demonstrates it, and how much remains to build. Our SQLx, Diesel and SeaORM comparison covers the separate choice of database workflow.

Use the acceptance matrix

Create two customer workspaces with known resources and at least two roles in one workspace. Run the permitted action first, then its forbidden counterpart. Otherwise, a test returning no data might be exercising an empty fixture or broken authentication rather than isolation.

This matrix is a proposed acceptance contract. It is not a report of tests already run against ShipRust or another starter. Replace the example resources and expected responses with your product's actual policy.

SurfacePermitted controlForbidden or changed-context control
Detail and list routesBob reads the workspace's known report and finds it in a listCarol cannot retrieve it by ID, search or pagination
WritesBob performs an action allowed to his roleA role without that permission cannot update or delete the report
Child resourcesA report references a dataset in its workspaceCreating a report with another workspace's dataset ID is rejected
Workspace switchingA member selects a workspace they belong toEditing the URL, header or request body cannot select an unauthorized workspace
Membership removalA current member can use the protected routeAfter removal, the existing session and personal API key lose workspace access; any independent service identity needs explicit authorization
Ownership transferThe new owner retains the workspace and its resourcesThe departing owner loses former permissions; deleting their personal account does not delete or strand the workspace
Background exportAuthorized work produces an artifact for the intended customerRemoval or resource transfer before execution follows the documented policy
CacheRepeated authorized reads return the correct customer resultThe same resource-like key under another workspace cannot reuse that result
File downloadAn authorized member retrieves a private exportA copied URL or object key does not grant another account access
Billing actionThe workspace's billing manager starts the allowed operationAn analyst or another workspace's owner cannot change that subscription
Connection reuseA transaction sees its own known tenant rowsThe next transaction without authorized context cannot inherit that identity

For writes, inspect stored state after denial. An error response is insufficient if the mutation happened before the permission check. For downloads, inspect the returned body, not merely the page containing the link. Public assets and deliberately shareable URLs need different expectations from private exports.

Record the revision, test identity and result for each row. Mark unexecuted cases as untested rather than assuming a library or compile-time check covers them. Give the same matrix to a coding agent: ask it to locate enforcement points and add missing behavior tests, not to declare the application secure from a source scan.

Separate billing from membership

For a team product, decide which entity buys access. A subscription attached to Alice personally is a different model from a subscription owned by Alice's workspace. Transferring the workspace should not accidentally strand payment ownership on a departing employee.

Write separate rules for membership, permissions and entitlement. Bob might remain a member after a subscription expires but lose access to a paid export. A billing manager might update payment details without permission to read every report. An application-wide administrator is not automatically a customer-workspace owner.

Seat billing adds another decision: which members count, when changes take effect, and what happens when payment fails. Do not infer a complete seat-management workflow from the existence of Stripe Checkout. Require the implementation to show how the subscription maps to the customer entity and how payment state changes affect the specific paid action.

ShipRust's Stripe guide and acceptance checklist describes its generated billing boundary and the product-specific work left to implement. This article's team-workspace model would require additional design; the checklist is a way to expose that work before purchase, not an assertion that the starter includes it.

Check what ShipRust includes

As checked November 5, ShipRust generates an Axum, Maud and SQLx/Postgres application with selectable integrations. With accounts enabled, it includes authentication, sessions, account settings and an application admin panel. The current template does not supply an organization-membership system, workspace roles, team invitations, seat billing or tenant RLS policies.

Its generated API and MCP options are extension points, not your finished business API. The current template API exposes application information and the authenticated account; its MCP server supplies a get_account tool. The ShipRust website's own project-generation API and tools are separate. Do not mistake those generator operations for endpoints your downloaded product already has.

Likewise, the included profile-picture storage example serves public avatars. User-prefixed storage keys do not make it a private workspace-document system. You must add and test authorization for private files.

Our source review covered the template's migrations, account API, MCP tool and avatar handler. This is our product assessment, not an independent security audit. The public project options guide explains what you can select before download.

ShipRust can fit a per-user product or a team willing to implement its own workspace domain on this stack. Prefer a starter with demonstrable organization workflows, or build your own foundation, when those workflows are the main reason you are buying. If your team already has maintained authentication and billing infrastructure, compare the integration and removal work before adding another foundation.

The regular price is $999 once for lifetime access to unlimited projects, with hosting and provider fees separate, as listed in the getting-started guide. Check current pricing and any active offer before purchasing. Pay for the reusable work you actually need, not for an assumed team feature list.

Copy the evaluation worksheet

Fill this in for each candidate using the same first paid workflow. Keep it with your evaluation notes or implementation PR. The blank result fields are intentional: a requirement becomes evidence only after someone checks it.

Candidate and inspected revision:
First paid customer workflow:
Customer boundary: user / workspace / other
Resource owner after its creator leaves:
Membership and role enforcement locations:
API-key and MCP permission/revocation policy:
Data boundary: scoped queries / RLS / separate databases
Runtime database role and policy-bypass check:
Worker authorization and private-file delivery policy:
Billing owner and seat-counting rule:
Acceptance-matrix results: passed / failed / untested, with evidence
Required custom work and implementation owner:
Purchase decision and unresolved blockers:

An unresolved ownership or permission rule is work to plan, not a reason to guess. If the missing work is your product's distinctive domain, implementing it may be entirely reasonable. If it is the boilerplate you intended to avoid, choose a closer fit. For ShipRust, review the generator's current options against the completed worksheet before deciding whether to purchase.