how to secure server actions the right way - By Sourav Mishra (@souravvmishra)

stop writing manual role checks. learn how to build a simple wrapper for your next.js server actions.

BySourav Mishra2 min read

security in next.js server actions is super important. a lot of people just hide the delete button in the ui and call it a day.

but if someone finds the url, they can still trigger the action!

here's how i secure everything using a simple wrapper pattern.

the annoying manual way

export async function deleteProduct(id: string) {
  const session = await auth();
  if (!session || session.user.role !== 'ADMIN') {
    throw new Error('nope');
  }
  // delete stuff
}

if you forget that if block just once, your app is completely open.


the wrapper pattern

let's make a wrapper function so we don't have to repeat ourselves.

1. make the wrapper

// lib/safe-action.ts
import { auth } from './auth';

type Role = 'ADMIN' | 'USER';

export function authAction(allowedRoles: Role[], action: Function) {
  return async (...args: any[]) => {
    const session = await auth();
    
    if (!session?.user) throw new Error('not logged in');
    if (!allowedRoles.includes(session.user.role)) throw new Error('not allowed');

    return action(...args);
  };
}

2. use it everywhere

now, wrapping your actions is super clean.

// actions/products.ts
import { authAction } from '@/lib/safe-action';

export const deleteProduct = authAction(['ADMIN'], async (id: string) => {
  await db.product.delete({ where: { id } });
});

why this is better

  1. less typing: you only write the auth check once.
  2. safer: you can easily see who is allowed to do what.
  3. cleaner: your actual logic isn't cluttered with if statements.

pro tip: never trust the client! just because a button is hidden doesn't mean the server is safe.

wanna know more about security? read my middleware post.


written by sourav mishra, just trying not to get hacked.

Share this post

Cover image for how to secure server actions the right way

You might also like

See all