Protect your Next.js application's API with Guards

MakerKit uses API Guards allow to protect your Next.js API endpoints from unauthorized users, bad input, etc.

API Guards are similar to Page Guards, except they belong to the server and will protect our API endpoints.

As an example, let's take MakerKit's implementation for handling user invites.

We want to check that:

  • the user is authenticated
  • the endpoint method being called is correct
import { withAuthedUser } from '~/core/middleware/with-authed-user';
export default function inviteHandler(
req: NextApiRequest,
res: NextApiResponse
) {
const handler = withPipe(
withMethodsGuard(SUPPORTED_METHODS),
withAuthedUser,
inviteMembersToOrganizationHandler
);
return withExceptionFilter(req, res)(handler);
}

The withPipe is a simple utility that can chain API handlers that respect the interface of a Next.js handler. The above handler can be written as follows, by passing in the parameters req and res explicitly:

export default withPipe(
(req, res) => withMethodsGuard(SUPPORTED_METHODS)(req, res),
(req, res) => withAuthedUser(req, res),
(req, res) => inviteMembersToOrganizationHandler(req, res)
);

The final inviteMembersToOrganizationHandler is the one that gets called at the end and is responsible for returning data to the client.

Makerkit Default API Guards

As you can see above, we're using various guards: withMethodsGuard and withAuthedUser.

These two guards will make sure that the request will get rejected if the user isn't authenticated or if the user submitted a request using a disallowed HTTP method.

You can also simplify the above if you only want to check if the user is signed-in:

export default function myAPIHandler(
req: NextApiRequest,
res: NextApiResponse
) {
await withAuthedUser(req, res);
// do something with res
}

withAuthedUser

The withAuthedUser API guard will reject the API request when the user is not authenticated.

withMethodsGuard

The withMethodsGuard API guard will reject the API request when the method is not among the ones used.

For example, the code below will never execute the handler myApiHandler if the method is not GET:

export default withPipe(
withMethodsGuard(['GET']),
myApiHandler
);

withAdmin

The withAdmin API guard will ensure the Firebase Admin is initialized before continuing with the rest of the handlers. This is useful before accessing any of the Firebase Admin packages.