Next.js Discord

Discord Forum

Server actions in custom domains in a multi-tenant application

Answered
joulev posted this in #help-forum
Open in Discord
Next.js limits the origins from which a server action can be executed [to prevent CSRF attacks](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#allowed-origins-advanced), and safe origins can be whitelisted manually by using serverActions.allowedOrigins.

But in a multi-tenant application, it may be possible for server actions to be submitted from a different subdomain, or even a user-provided custom domain. So serverActions.allowedOrigins is not possible.

What can I do to make server actions still run in custom subdomains/user domains, while still being safe against CSRF attacks?
Answered by joulev
so i can overcome this limitation by faking the origin header in the logic that handles multi-tenancy. for example in the cloudflare worker, i do this
// @ts-check
export default {
  /**
   * @param {Request} request 
   */
  fetch(request) {
    const { pathname, searchParams } = new URL(request.url);
    const query = searchParams.toString();
    const path = `${pathname}${query.length > 0 ? `?${query}` : ""}`;
    
    const proxiedRequest = new Request(
      `https://debug-git-server-action-joulev-proj.vercel.app${path}`,
      request
    );
    proxiedRequest.headers.set(
      "origin",
      "https://debug-git-server-action-joulev-proj.vercel.app"
    );
    return fetch(proxiedRequest);
  },
};

then i can get the server action to run on the worker, despite the worker not being run on this vercel.app domain but on an entirely different domain.

now this will make my server action vulnerable to CSRF. but i guess since all user domains are accepted, i should just generate a CSRF token myself and allow all domains, rather than verifying the user domain before overriding (which can take long). this is a good enough compromise.
View full answer

1 Reply

so i can overcome this limitation by faking the origin header in the logic that handles multi-tenancy. for example in the cloudflare worker, i do this
// @ts-check
export default {
  /**
   * @param {Request} request 
   */
  fetch(request) {
    const { pathname, searchParams } = new URL(request.url);
    const query = searchParams.toString();
    const path = `${pathname}${query.length > 0 ? `?${query}` : ""}`;
    
    const proxiedRequest = new Request(
      `https://debug-git-server-action-joulev-proj.vercel.app${path}`,
      request
    );
    proxiedRequest.headers.set(
      "origin",
      "https://debug-git-server-action-joulev-proj.vercel.app"
    );
    return fetch(proxiedRequest);
  },
};

then i can get the server action to run on the worker, despite the worker not being run on this vercel.app domain but on an entirely different domain.

now this will make my server action vulnerable to CSRF. but i guess since all user domains are accepted, i should just generate a CSRF token myself and allow all domains, rather than verifying the user domain before overriding (which can take long). this is a good enough compromise.
Answer