> ## Documentation Index
> Fetch the complete documentation index at: https://vlyai-1c28d863.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Data access control

> Per-user, per-workspace, per-team scoping patterns. Defending against data leaks.

The most common security bug in multi-user apps is **IDOR** — Insecure Direct Object Reference. User A passes a URL with user B's resource ID; the server returns it without checking. Don't let this happen.

## The rule

**Every query and mutation that returns or modifies data must verify the caller has access.**

## Patterns

### Per-user

```typescript theme={null}
const u = await ctx.auth.getUserIdentity();
if (!u) throw new Error("Not signed in");

const item = await ctx.db.get(itemId);
if (!item) throw new Error("Not found");
if (item.ownerId !== u.subject) throw new Error("Permission denied");
```

### Per-workspace

```typescript theme={null}
const u = await ctx.auth.getUserIdentity();
if (!u) throw new Error("Not signed in");

const item = await ctx.db.get(itemId);
const membership = await ctx.db
  .query("memberships")
  .withIndex("by_workspace_and_user",
    q => q.eq("workspaceId", item.workspaceId).eq("userId", u.subject))
  .unique();
if (!membership) throw new Error("Permission denied");
```

### Per-role

```typescript theme={null}
if (!["admin", "owner"].includes(membership.role)) {
  throw new Error("Admin only");
}
```

## Common mistakes

<AccordionGroup>
  <Accordion title="Filtering only on the client" icon="alert-triangle">
    "I only show the user their own tasks in the UI" — but the API endpoint returns everything. Anyone who sniffs the network can see all tasks.
  </Accordion>

  <Accordion title="Trusting URL params" icon="alert-triangle">
    `/tasks/123` — server should verify the user has access to task 123, not just trust the URL.
  </Accordion>

  <Accordion title="Forgetting the membership check on workspace queries" icon="alert-triangle">
    Filtering by `workspaceId` is necessary but not sufficient. Verify the user is a member of that workspace too.
  </Accordion>

  <Accordion title="Returning related data without scoping" icon="alert-triangle">
    A query for "task with author" might return the author's full record. Scope what you return.
  </Accordion>
</AccordionGroup>

## Tips

<Tip>
  **Write a "permission helper" once and reuse.** A `requireMembership(ctx, workspaceId)` function called from every mutation prevents copy-paste drift.
</Tip>

<Tip>
  **Test by signing in as different users.** Don't trust that vly generated the right checks — verify by trying to access other users' data.
</Tip>

## Related

<CardGroup cols={3}>
  <Card title="Roles & permissions" icon="shield-check" href="/features/auth/roles-and-permissions">
    Role model.
  </Card>

  <Card title="Multi-tenancy" icon="building" href="/architecture/multi-tenancy">
    Per-tenant patterns.
  </Card>

  <Card title="Auth integration" icon="lock" href="/convex/auth-integration">
    The `ctx.auth` API.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.