#Query & mutation interceptors for user permissions

4 messages · Page 1 of 1 (latest)

haughty palm
#

Am building an app with 4 different customer types each has their own dashboard to manage parts of the platform, right now I split the queries and mutations into 4 folders for each to easily manage them but I want to add a layer on top of each group of queries and mutations that can always run a check if this user is allowed to perform those actions, something like an intercepter. how can I achieve that?

brave graniteBOT
#

Hi @haughty palm. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

You can achieve query and mutation interception for user permissions in Convex by using custom function wrappers (sometimes called "middleware") that enforce authorization logic before your actual handler runs. This is a recommended pattern in Convex for consistent, reusable access control.

How to Implement

  1. Create a custom wrapper for each customer type
    Define a function (e.g., withCustomerTypeA, withCustomerTypeB, etc.) that checks if the current user has the right permissions for that customer type. This wrapper will run before your query or mutation logic.

  2. Wrap your queries and mutations
    Instead of exporting raw queries/mutations, export them wrapped with your custom function. This ensures the check always runs.

Example: Custom Wrapper

export const withCustomerTypeA = (func) => {
  return async (ctx, ...args) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity || identity.customerType !== "A") {
      throw new Error("Unauthorized");
    }
    return await func({ ...ctx, user: identity }, ...args);
  };
};

Example: Using the Wrapper

export const getDashboardData = query(
  withCustomerTypeA(async (ctx, args) => {
    // Your logic here, ctx.user is guaranteed to be a type A customer
  })
);

You can generalize this pattern for each customer type, or even parameterize it for more flexibility.

Why This Works

  • Centralizes authorization logic: You only need to update the wrapper if your rules change.
  • Prevents accidental bypass: All queries/mutations for a customer type must go through the check.
  • Composable: You can combine wrappers for more complex rules (e.g., team membership, admin status) see example of composition.
#

Convex Best Practices

  • Use custom functions to wrap your queries/mutations and inject logic into the context.
  • Consider using ESLint rules to enforce that only wrapped functions are used.
  • You can also use row-level security for per-document checks, but for your use case, function-level wrappers are likely simpler and more efficient.

References:

If you need to inject more data (like user roles or team info), you can extend the context in your wrapper before passing it to the handler. This approach is idiomatic in Convex and scales well as your app grows.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: