Next.js Discord

Discord Forum

Expiring a Cookie through an API Route when called by server component?

Answered
Persian posted this in #help-forum
Open in Discord
PersianOP
I'm doing some small tests and trying to expire a cookie through a server side component. In my actual project I've already invalidated the session ID stored in my DB so even with the cookie still there it wont auth, but I would still like to do a clean up and expire the cookie when a user logs out.

Here is a demo:

src->app->logout->page.js
async function handleLogout() {
    try {
        const response = await fetch(`http://localhost:3001/api/logout`, {method: 'POST'});
        const { message } = await response.json();
        console.log("Done -> ", message);
    } catch (e) {
        console.error('Error during logout:', e);
    }
}

export default async function LogoutPage() {
    await handleLogout();

    return (
        <div>
            <h2>Logged out.</h2>
        </div>
    );
}


src->app->api->logout->route.js
import { NextResponse } from 'next/server';

export async function POST() {
    const response = NextResponse.json({ message: 'Cookie expired' });
    response.cookies.set('sessionid', '', {
        expires: new Date(0),
        httpOnly: true,
        path: '/'
    });

    return response;
}


When I refresh the browser the cookie is still there with the same Expires/Max-Age value as when they logged it.

For reference this is how the cookie is made in the Login API Route:

genID is done through crypto. Everything works post Login, I just cannot remove or expire the cookie on the client side through server side actions or using the API Route via a POST request within a server component.

const res = NextResponse.json("", {status: 200});
res.cookies.set("sessionid", genID, {
    path: '/',
    httpOnly: true,
    maxAge: 1000
});


Thank you for any help. πŸ™‚
Answered by Jboncz
Sec
View full answer

22 Replies

PersianOP
I've done some more research and I'm not sure if this is the only solution but it seems I cannot do anything with cookies due to streaming on the server side like this. One solution seems to be to make a client component which handles that fetch request to my API Route to alter the cookie, then include that into my server component so it runs on page load.

Is this pretty much the only way I can handle this?
Yep! Server Components cannot modify cookies, they can read them though.
@Jboncz Yep! Server Components cannot modify cookies, they can read them though.
PersianOP
Thank you. I guess this will be the way going forward. I just made a client side component that does the fetch and I import it into my server side component. Works fine. I'll keep using the API Route way since these are httponly.
Can also use a server action if you want πŸ™‚
PersianOP
How would I use a server action for this?

Here is my code by the way for the Client Side Component:

'use client'
import { useEffect } from 'react';

async function ExpireCookie() {
    useEffect(() => {
        async function SetCookie() {
            await fetch('http://localhost:3001/api/logout', {method: 'POST'});
        }

        SetCookie();
    }, []); 

    return null;
}

export default ExpireCookie;


Then my API Route:

import { NextResponse } from 'next/server';

export function POST() {
  const res = NextResponse.json("Cookie expired", { status: 200 });
  res.cookies.set("sessionid", "", {
    path: '/',
    httpOnly: true,
    expires: Date.now(),
  });
  return res;
}


Then server component:

import ExpireCookie from '../components/ExpireCookie';

export default async function Logout() {
    return (
      <div>
        <ExpireCookie />
        <h2>Testing Logout</h2>
      </div>
    );
}
@Jboncz How would I change this for a Server Action? I've seen briefly these done for say form, but not cookies.
Sec
Answer
Server Action
'use server'
 
import { cookies } from 'next/headers'
 
async function ExpireCookies(data) {
  try{
       cookies().set({
      name: 'sessionID',
      valie: 'boo'
      expires: Date.now()
      httpOnly: true,
      path: '/',
    })

  return true;

  }
  catch (e){
    return false;
  }
}


'use client'
import { useEffect } from 'react';

async function ExpireCookie() {
    useEffect(() => {
        async function SetCookie() {
            const result = await ExpireCookies();
            if(!result){
              console.log('issues when expiring cookie')
            }
        }

        SetCookie();
    }, []); 

    return null;
}

export default ExpireCookie;


Server Component
import ExpireCookie from '../components/ExpireCookie';

export default async function Logout() {
    return (
      <div>
        <ExpireCookie />
        <h2>Testing Logout</h2>
      </div>
    );
}
Just removes the fetch handling stuff, I prefer it over using api routes.
PersianOP
Okay, I'll try this out and report back. Also I think I'm not supposed to have my Client Side component with Async. Just the function within useEffect. My mistake.
when its applicable, there are still a few things to use api routes for though
Thats true, didnt see that hahah. Im not in my editor, just writing on discord πŸ™‚
PersianOP
I was copying and pasting stuff from my server side things so I left it there by mistake.
πŸ˜„
Heres more on server actions
Doesnt have to be only in a form, its pretty nice.
export default function Page() {
  async function createInvoice(formData: FormData) {
    'use server'
 
    const rawFormData = {
      customerId: formData.get('customerId'),
      amount: formData.get('amount'),
      status: formData.get('status'),
    }
 
    // mutate data
    // revalidate cache
  }
 
  return <form action={createInvoice}>...</form>
}


You can even make a form and forget about the client side stuff.
your sign out button can be a form that calls the server action
PersianOP
Ooo this is really neat. I'll read up and once I've tried this I'll comment back.
PersianOP
@Jboncz I went through both ways to do it and have it working perfectly with either API Routes or Server Actions. Thank you again. πŸ‘
No problem bud happy to help @Persian