teleport-be: The Laravel Backend That Treats Tenant Boundaries Like a Security Feature
A close look at a JWT-authenticated, UUID-first, multi-tenant API that creates a user’s first organization atomically, blocks cross-tenant leaks, and bends Laravel toward serverless deployment.
- The repo’s core idea is that tenant boundaries should be enforced as a security primitive, not treated as a view-layer concern.
- Signup is atomic, so a user and their default organization are created together or not at all.
- Cross-tenant access is relationship-aware, because the API checks whether two users share an organization before revealing data.
- UUIDs, helper-driven responses, and explicit JWT middleware make the backend feel more intentional than a typical task implementation.
Most backends authenticate a user, then hope the rest of the system behaves. teleport-be does something sharper: it treats organization membership as the thing that decides what a user can see. That makes the repo interesting even before you look at the stack.
The project is a Laravel 11 API with JWT auth, PostgreSQL, UUID primary keys, and a serverless deployment shape for Vercel. But the real story is the security model. It does not just ask, “Is this user signed in?” It asks, “Do these two users share a common organization?”
Why this backend is really about tenant boundaries
That shared-organization check is the first thing worth noticing. In a multi-tenant system, the mistake is usually subtle: a record is technically private, but one missing filter leaks it across workspace lines. This repo pushes the opposite habit. Privacy is encoded in the access rule itself.
The repository’s behavior fits a SaaS pattern that feels familiar, but the enforcement is stricter than usual. A user can belong to multiple organizations, and access to another user’s profile depends on whether their memberships intersect. That is relationship-aware authorization, not just session validation.
Signup is atomic, not improvised
The registration flow is built like a transaction should be built. User creation and default organization creation happen together, inside a database transaction. If the second step fails, the first one is rolled back. That prevents orphaned accounts and half-formed tenant records.
That matters because onboarding is where tenant systems get messy. If the first organization is created as an afterthought, the codebase starts accumulating edge cases. Here, the default organization is part of the identity lifecycle, which is cleaner and safer.
| Model | Rule | Strength | Weakness |
|---|---|---|---|
| Naive auth | Signed in means allowed | Easy to implement | Leaks data across tenant lines |
| Single-tenant auth | User belongs to one workspace | Simple mental model | Breaks down when users span multiple organizations |
| Relationship-aware auth | Access only if memberships intersect | Matches real multi-tenant privacy | Needs explicit relationship checks |
The UUID-first model changes how the system behaves
Both the User and Organisation models use UUIDs, which is not cosmetic. Sequential IDs are convenient for humans and awkward for privacy. UUIDs make object identity harder to guess and better suited to APIs that expose records beyond a single browser session.
The repo also uses a manual public-column pattern. Instead of dumping the entire model into JSON, it exposes a curated set of fields through a helper method. That is a lightweight privacy boundary, and it is a good one. It makes the shape of the API obvious without introducing a heavy resource layer.
public function getPublicColumns(): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
];
}
That pattern signals discipline. The code is small, but it behaves as if the developer expects data exposure to be a real risk. That is the right instinct for multi-tenant software.
How authorization actually works across organizations
The most interesting line in the repository is the one that intersects memberships. It turns authorization into a relationship problem. The app does not simply compare user IDs or trust a role flag. It asks whether the authenticated user and the target user touch the same organization set.
That is a stronger default than many SaaS apps ship with. A lot of systems only think in terms of “this workspace” and “that workspace.” Here, the model can handle users who belong to multiple organizations, which is exactly where bugs tend to appear.
| Question | Weak answer | What teleport-be does |
|---|---|---|
| Can the requester view this user? | Yes, if logged in | Only if both users share an organization |
| What is the authorization unit? | The session | The membership graph |
| What blocks leakage? | Ad hoc controller checks | Explicit intersection logic |
That is why the auth code feels mature. It is not just protecting endpoints. It is encoding a policy about relationships.
ResponseHelper makes the API predictable
The repo includes a centralized response helper, and that usually sounds like housekeeping until you compare it with the usual controller sprawl. A stable response shape lowers friction for frontend integration and makes errors easier to handle consistently.
return ResponseHelper::success(
'User retrieved successfully',
$user->getPublicColumns()
);
This is especially helpful in a task-oriented codebase. Even when the app is small, the response format suggests someone was thinking about future consumers, not just the current endpoint.
JWT and middleware keep the edge cases explicit
The middleware does what good middleware should do. It handles invalid and expired tokens directly, instead of letting everything collapse into a generic failure. That makes auth mistakes easier to debug and reduces the chance that clients misread a token problem as a server outage.
JWT is not the interesting part on its own. The interesting part is the refusal to let token errors stay vague. That is what makes the API feel deliberate.
Why Vercel changes the shape of a Laravel app
The deployment story is where the repo stretches Laravel into a serverless shape. Files like api/index.php and vercel.json suggest the app has been adapted for a platform that expects stateless function entry points. That changes how routing and bootstrapping have to work.
The point is not whether Laravel can run there. It can. The point is that a traditional PHP framework has to be nudged into a different runtime model, and this repository shows the sort of glue code that makes that possible.
That makes the project feel less like a standard backend template and more like an implementation of a deployment constraint. The architecture follows the target platform, not the other way around.
What this repo gets right, and what still looks experimental
The strongest parts are clear. Transactions protect signup, UUIDs protect identity exposure, and membership intersection protects tenant boundaries. Those are not decorative choices. They are the foundation of a serious multi-tenant backend.
What looks less finished is also easy to spot: commented-out code and the absence of a visible CI/CD pipeline. That does not weaken the core idea, but it does suggest a project that solves the task well without yet looking like a fully hardened platform.
| Strength | Why it matters | What it suggests |
|---|---|---|
| Atomic record creation | Prevents partial onboarding state | Good transactional discipline |
| Shared-org authorization | Blocks cross-tenant leaks | Security-first design |
| Commented-out deployment code | Signals active iteration | Still in task-implementation mode |
| No visible CI/CD | Fewer guardrails | Not yet production-polished |