Protect Server Actions with Next Auth in Next JS 14

56 views Asked by At

I just created my first Next JS 14 App with App Router v4 authentication and authorization.

I was so proud, too, everything works great! There is just one caveat I cannot seem to solve: Server Actions. They are supposed to replace API routes, making things a lot smoother and easier. They do work perfectly, but the problem is: I cannot protect them.

App setup: App folder structure with t "actions" folder side by side the app folder. It contains all server actions.

One of the files is db.js containing all database related logic, including this piece of code:

export async function executeQuery(query, values = []) {
  // Ensure that the connection pool is initialized
  
  await connectDatabase();

  // Execute the query using the connection pool
  const connection = await pool.getConnection();
  try {
    const [results] = await connection.execute(query, values);
    return results;
  } finally {
    connection.release(); // Release the connection back to the pool
  }
}

This basically does all database queries with a normal mysql2 database, I am not using an adapter.

All relevant routes are protected in the middleware (here just an example):

export { default } from "next-auth/middleware";

export const config = { matcher: ["/profile" "/dashboard" ] };

Now, when I call "executeQuery" on the public route "home" like this (did it just to test this problem):

import {useSession} from "next-auth/react";
import { executeQuery } from "../actions/db";
import { useState } from "react"; 

export default /*async*/ function Home() {
  const session = useSession();
  const [data, setData] = useState(null);

  return (
    <div className="base-container" style={{ textAlign: "center", flexDirection: "column" }}>
      <h1>Home</h1>
      {session?.user?.firstname}, {session?.user?.lastname}
      <input type="text" name="id" className="base-input" style={{border: "1px solid white"}}></input>
      <button onClick={async () => {
        let id = document.querySelector('input[name="id"]').value;
        const result = await executeQuery("SELECT * FROM users WHERE id = ?", [id]);
        setData(JSON.stringify(result[0]));
      }}> Get email for input user</button>
      <label style={{border: "1px solid white"}}>{data}</label>
    </div>
  );
}

Here, the firstname/lastname only show up when logged in, all good.

I was expecting for Next Auth to prohibit at least the usage of the server action when not logged in here, but that was not the case. As the server functions are also used before loggin in and they should also only be used in a certain way, that would also not help completely.

Putting in a random ID into the input field (which matches an ID in the database; and yes I could use UUIDs, but I don't want to rely on only that, or do people do that?) I can pull all data related to "users" in the database; I could even put in more elaborate queries and get everything I want.

That is of course an unacceptable security issue.

The only way I can think of right now is manually check every single server action call whether they are ok to make it. But this cannot be the right way, as this is prone to errors, too.

How do I correctly secure server actions (this one but also others that may be sensitive)? Or do I have to use APIs after all, as API routes can be protected with Next Auth easily...?

I am a little stumped here, so I would really appreciate some input on how this is supposed to work.

0

There are 0 answers