Skip to main content

Backend Authorization

Enforce ownership, membership, and role rules inside Convex functions.

The backend is the only authoritative access boundary. Every protected Convex function checks the caller and the requested resource.

Start with identity

convex/lib/authz.ts
import { ConvexError } from 'convex/values'
import type { MutationCtx, QueryCtx } from '../_generated/server'

type Ctx = QueryCtx | MutationCtx

export async function requireIdentity(ctx: Ctx) {
  const identity = await ctx.auth.getUserIdentity()
  if (!identity) {
    throw new ConvexError({ code: 'UNAUTHENTICATED' })
  }
  return identity
}

Check the resource

convex/projects.ts
export const rename = mutation({
  args: {
    projectId: v.id('projects'),
    name: v.string(),
  },
  handler: async (ctx, args) => {
    const identity = await requireIdentity(ctx)
    const project = await ctx.db.get(args.projectId)

    if (!project || project.ownerId !== identity.subject) {
      throw new ConvexError({ code: 'PROJECT_NOT_FOUND' })
    }

    await ctx.db.patch(project._id, { name: args.name.trim() })
  },
})

Returning the same not-found response for missing and unauthorized resources can avoid revealing that a resource exists.

Organization membership

For organization data:

  1. Resolve the authenticated user.
  2. Load membership by indexed organization/user fields or through the canonical Better Auth organization API.
  3. Check the required permission.
  4. Load or write the resource only after access is established.

Keep the helper in application Convex code. Better Convex Nuxt does not own your role vocabulary.

Frontend capability context

A query may return the caller's role and allowed UI capabilities. This prevents rendering controls that will fail, but protected mutations repeat the backend check.

Test the matrix

At minimum, test:

  • anonymous caller;
  • resource owner/member;
  • authenticated non-member;
  • wrong organization;
  • missing resource;
  • each role that changes a permission.

Tests should call Convex functions directly. A browser redirect does not verify backend denial.