Documentation
Authentication

Permissions and access control

Basic access control

Of course you want to protect certain queries or mutations of your API to be only accessible by authenticated users, specific roles or even specific criteria of each user.

With supastarter you can control the access of each query/mutation individually in the resolver function. From the context of the resolver you have access to the following data:

  • user: The information of the authenticated user (always defined if you use a protected procedure)
  • session: The session of the user (always defined if you use a protected procedure)
  • abilities: A helper function to check the permissions of the user
  • isAdmin: A boolean if the request is made from the adminCaller which means it is called from the server (useful if you want to allow certain actions from the server without having a user session)
  • teamMemberships: An array of team memberships of the user

Besides this information there are three kinds of procedures you can use:

  • publicProcedure: This procedure is accessible by everyone
  • protectedProcedure: This procedure is only accessible by authenticated users
  • adminProcedure: This procedure is only accessible by authenticated users with the role admin

To protect a procedure so that only authenticated users can request it you can use the protectedProcedure function like so:

import { protectedProcedure } from "../../../trpc/base";
 
export const myProtectedProcedure = protectedProcedure.query(
  async ({ input }) => {
    return `Hello ${input}`;
  },
);

If someone tries to call this endpoint without being authenticated, they will receive a 401 Unauthorized error.

In case you want to have a procedure that is available for everyone but only returns certain data for authenticated users, use the publicProcedure function and check the user in the resolver:

import { publicProcedure } from "../../../trpc/base";
 
export const myPublicProcedure = publicProcedure.query(
  async ({ input }, { user }) => {
    if (user) {
      return `Hello ${user.name}`;
    }
 
    return `Hello anonymous`;
  },
);

Fine-grained access control

If you want to have more fine-grained access control you can use the abilities helper.

In the packages/api/modules/auth/abilities.ts file you will find a function called defineAbilitiesFor which takes the user and it's team memberships and returns a set of abilities. You can customize this function to your needs and use it in your procedure to check for any kind of permission.

Per default there are three abilities you can check for:

  • isAdmin: If the authenticated user has the role admin
  • isTeamMember(teamId): If the authenticated user is a member of the team with the given id
  • isTeamOwner(teamId): If the authenticated user is the owner of the team with the given id

Here are a few examples on how to use the abilities helper:

import { protectedProcedure } from "../../../trpc/base";
 
export const myProtectedProcedure = protectedProcedure.query(
  async ({ input }, { user, abilities }) => {
    if (!abilities.isAdmin) {
      throw new TRPCError({
        code: "FORBIDDEN",
      });
    }
 
    // do something only an admin can do...
  },
);
import { protectedProcedure } from '../../../trpc/base';
 
export const myProtectedProcedure = protectedProcedure
.input(z.object({
  teamId: z.string()
})
.query(async ({ input }, { user, abilities }) => {
  if (!abilities.isTeamMember(input.teamId)) {
    throw new TRPCError({
      code: 'FORBIDDEN',
    });
  }
 
  // do something only a team member can do...
});
import { protectedProcedure } from '../../../trpc/base';
 
export const myProtectedProcedure = protectedProcedure
.input(z.object({
  teamId: z.string()
})
.query(async ({ input }, { user, abilities }) => {
  if (!abilities.isTeamOwner(input.teamId)) {
    throw new TRPCError({
      code: 'FORBIDDEN',
    });
  }
 
  // do something only a team owner can do...
});

On this page