Next.js Discord

Discord Forum

Error handling for Server Actions with middleware

Answered
Mugger Crocodile posted this in #help-forum
Open in Discord
Mugger CrocodileOP
I have a simple client component:

"use client";

import { serverFunction } from "../../server-function";

export default function TestPage() {
  const handleClick = async () => {
    try {
      const data = await serverFunction();

      console.log(data);
    } catch (e) {
      console.log("Response:", e);
    }
  };

  return (
    <>
      <button type="button" onClick={handleClick}>
        Trigger server function
      </button>
    </>
  );
}


With an even simpler server action:
"use server";

export async function serverFunction() {
  "use server";
  console.log("a function on the server");

  return { success: true };
}


In my middleware I do a check on all routes on whether the user is logged in. If not, I return a 403 error from the middleware.

My question is, how do I capture this error and handle it appropriately? In my above client component the try/catch block is ineffective as the catch block never resolves. I can't put the error handling in the server function itself as it never gets reached since the middleware intercepts the response.

In my testing it seems that the 403 error is shown in the browser console, but it doesn't bubble up to the client component. The line console.log(data) in the above client component will log undefined in the case of a 403.
Answered by Jboncz
Okay, what I do is in middleware as part of my authentication verification I do

const serverAction = request.headers.get('next-action');

//Invalid auth section
if(serverAction != null){
    const response = NextResponse.next()
    response.headers.set('mw-action-auth','false')
    return response;
}


Then I have a helper function I execute at the top of all my server actions

import {headers} from 'next/headers';
export async function serverActionAuth(){
  const authValue = headers().get('mw-action-auth');
  if(authValue == false){
    return {authentication: false}
  }
  else{
    return
  }
}


At this point you have everything you need to let it pass and go to the server actions, a benefit to this is you can choose to include the 'serverActionAuth' function in a server action if its a protected functionality.'
View full answer

23 Replies

Mugger CrocodileOP
Forgot to mention this is on Next 14
Velvet ant
do you try to use useFormState or useActionState (depends on the next version) ?
Mugger CrocodileOP
Seems like those are only available in canary versions of React, so unfortunately I can't use those (yet)
Velvet ant
What react version do you have actually ?
Velvet ant
It seems that you are able to use useFormState with next 14

https://nextjs.org/blog/next-14#server-actions-stable
Mugger CrocodileOP
You're right, I was able to use useFormState with react-dom since I'm using react 18.3.1. Unfortunately though it doesn't work, because the middleware intercepts the request to the server function (even when using useFormState) and returns a 403. The console shows this part failing in particular: https://github.com/vercel/next.js/blob/48f62e059317f78f07d71541a20e81d68f78e298/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts#L62-L82

Since the server action is never actually executed, I can't return an error from it. I also tried having a generic error boundary as documented here: https://rc.nextjs.org/docs/app/building-your-application/routing/error-handling#using-error-boundaries but it's never hit despite the 403 error showing up in the browser console.
Velvet ant
is it possible to see a example of your middleware ?
Fix server action edge redirect with middleware rewrite (#67148)
If your trying to rewrite via middleware, either that, or in middleware you append a header with something that indicates that its a bad request and on the server component return a {error: middleware rejected} at the begining and key off of it.
It prevents REAL errors from being returns to avoid data leakage into the client side
You have to very intetionally return what you want to return.
Mugger CrocodileOP
Thanks @Jboncz, but that unfortunately doesn't help me. The problem is that the middleware intercepts the request to the server action so any error handling in the action itself is never executed. Here's an example of what I'm trying to accomplish:

1. User logs in to application and session is created
2. Each time user navigates or calls a server action, middleware checks to see if the session is valid
3. After N time, the session is invalidated due to timeout
4. User, who still has a tab open, clicks a button that calls a server action
5. Middleware checks to see if the session is valid, sees that it isn't and returns a 403 error
6. 403 error is now displayed in the browser, but there doesn't seem to be any way for me to catch said error in the client side code (see code in OP)

Since in this scenario the server action is never actually executed, any error handling there won't do anything. What I'm essentially trying to do is catch the 403 error on the client so I can either display an error message or redirect to the login page.
Okay, what I do is in middleware as part of my authentication verification I do

const serverAction = request.headers.get('next-action');

//Invalid auth section
if(serverAction != null){
    const response = NextResponse.next()
    response.headers.set('mw-action-auth','false')
    return response;
}


Then I have a helper function I execute at the top of all my server actions

import {headers} from 'next/headers';
export async function serverActionAuth(){
  const authValue = headers().get('mw-action-auth');
  if(authValue == false){
    return {authentication: false}
  }
  else{
    return
  }
}


At this point you have everything you need to let it pass and go to the server actions, a benefit to this is you can choose to include the 'serverActionAuth' function in a server action if its a protected functionality.'
Answer
tldr, instead of returning 403 return {auth:false} from server action and handle the rejection client side however you wish
@riský you been using server actions alot? This still a good way to handle middleware level authentication in respect to server actions?
if the user should ever see it, then i give nice error, and if not i just throw, but you cant return 403 in server action (either error code or 200)
idk what middleware can do to it lol
Invalidate authentication mid flight, but honestly you could just check the authentication in the server action
Mugger CrocodileOP
Thanks for all your help @Jboncz, this is for a library that I'm building so I guess the best solution is to document that you should be checking auth in server actions. I'll also provide a helper function like yours to make it easy.
No problem. Make sure you mark an answer so the thread closes