Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set cookie doesn't work in Router handler nor server action #70068

Open
1 task done
t18n opened this issue Sep 13, 2024 · 5 comments
Open
1 task done

Set cookie doesn't work in Router handler nor server action #70068

t18n opened this issue Sep 13, 2024 · 5 comments
Labels
examples Issue/PR related to examples

Comments

@t18n
Copy link

t18n commented Sep 13, 2024

Verify canary release

  • I verified that the issue exists in the latest Next.js canary release

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 23.4.0: Fri Mar 15 00:10:42 PDT 2024; root:xnu-10063.101.17~1/RELEASE_ARM64_T6000
  Available memory (MB): 32768
  Available CPU cores: 10
Binaries:
  Node: 20.8.1
  npm: 10.1.0
  Yarn: 1.22.22
  pnpm: 8.9.2
Relevant Packages:
  next: 14.2.2 // There is a newer version (14.2.11) available, upgrade recommended!
  eslint-config-next: 14.2.2
  react: 18.2.0
  react-dom: 18.2.0
  typescript: 5.2.2
Next.js Config:
  output: N/A
 ⚠ There is a newer version (14.2.11) available, upgrade recommended!
   Please try the latest canary version (`npm install next@canary`) to confirm the issue still exists before creating a new issue.
   Read more - https://nextjs.org/docs/messages/opening-an-issue

Which example does this report relate to?

none

What browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

Describe the Bug

I am trying to set up an auth page and hit some errors while trying to set cookies from the server side, despite setting them in Server Action and Router Handler as the documentation stated. The idea is when the user visit the auth page, for example http://localhost:3010/auth?redirectTo=/en&tokenCsrf=1234&tokenRefresh=1234&tokenAccess=1234, the mock cookies are set to the browser during the SSR and redirect the user to the redirectTo page.

Here is my attempt to use Router Handler

// app/auth.tsx

import { AuthPageComponentParams } from '@/components/AuthPage';
import { BasePageProps } from '@/types/page';
dimport { redirect } from 'next/navigation';

type AuthPageParams = {
  searchParams: {
      tokenAccess?: string;
  tokenRefresh?: string;
  tokenCsrf?: string;
redirectTo?: string;
};
};

export default async function AuthPage({ searchParams }: AuthPageParams) {
  console.log('AuthPage', searchParams);

  await fetch('http://localhost:3010/api/auth/mock', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      credentials: 'include'
    },
    body: JSON.stringify(searchParams)
  });

  redirect(searchParams.redirectTo || '/');
}
// app/api/auth/mock

import { CONFIG } from '@/config/config';
import { HTTP_STATUS } from '@/constants/http_status';
import { MOCK_AUTH_COOKIES } from '@helper/constants';
import { ResponseCookie } from 'next/dist/compiled/@edge-runtime/cookies';
import { cookies } from 'next/headers';
import { NextRequest, NextResponse } from 'next/server';

export type AuthCookies = {
  tokenAccess?: string;
  tokenRefresh?: string;
  tokenCsrf?: string;
};

export const mockAuthCookieConfig: Cookies.CookieAttributes = {
  domain: `.localhost`,
  path: '/',
  secure: true,
  sameSite: 'lax' // NOTE: To avoid URL for breaking when clicked from an email.
};

type Body = AuthCookies;

export async function POST(request: NextRequest) {
  const { tokenAccess, tokenRefresh, tokenCsrf }: Body = await request.json();

  try {
    const cookieStore = cookies();

    if (!CONFIG.auth.mock && CONFIG.environment !== 'development_local' && tokenAccess && tokenRefresh && tokenCsrf) {
      return;
    }

    if (tokenAccess && tokenRefresh && tokenCsrf) {
      console.log('Setting mock auth cookies in the browser...');
      const oneMonth = 24 * 60 * 60 * 30;

      const cookiePairs = {
        [MOCK_AUTH_COOKIES.MOCK_CSRF_TOKEN]: tokenCsrf,
        [MOCK_AUTH_COOKIES.MOCK_REFRESH_TOKEN]: tokenRefresh,
        [MOCK_AUTH_COOKIES.MOCK_ACCESS_TOKEN]: tokenAccess
      };

      Object.entries(cookiePairs).forEach(([k, v]) => {
        cookieStore.set(k, v, {
          ...mockAuthCookieConfig,
          expires: oneMonth
        } as ResponseCookie);
      });

      console.log('Mock auth cookies set successfully', {
        [MOCK_AUTH_COOKIES.MOCK_CSRF_TOKEN]: cookieStore.get(MOCK_AUTH_COOKIES.MOCK_CSRF_TOKEN),
        [MOCK_AUTH_COOKIES.MOCK_REFRESH_TOKEN]: cookieStore.get(MOCK_AUTH_COOKIES.MOCK_REFRESH_TOKEN),
        [MOCK_AUTH_COOKIES.MOCK_ACCESS_TOKEN]: cookieStore.get(MOCK_AUTH_COOKIES.MOCK_ACCESS_TOKEN)
      });
    }
  } catch (e) {
    console.error(e);
  }
  return NextResponse.json({ ok: true, status: HTTP_STATUS.SUCCESS });
}

The endpoint is called properly, in the server log, I see

Setting mock auth cookies in the browser...
[0] Mock auth cookies set successfully {
[0]   MOCK_CSRF_TOKEN: {
[0]     name: 'MOCK_CSRF_TOKEN',
[0]     value: '1234',
[0]     domain: '.localhost',
[0]     path: '/',
[0]     secure: true,
[0]     sameSite: 'lax',
[0]     expires: 1970-01-01T00:43:12.000Z
[0]   },
[0]   MOCK_REFRESH_TOKEN: {
[0]     name: 'MOCK_REFRESH_TOKEN',
[0]     value: '1234',
[0]     domain: '.localhost',
[0]     path: '/',
[0]     secure: true,
[0]     sameSite: 'lax',
[0]     expires: 1970-01-01T00:43:12.000Z
[0]   },
[0]   MOCK_ACCESS_TOKEN: {
[0]     name: 'MOCK_ACCESS_TOKEN',
[0]     value: '1234',
[0]     domain: '.localhost',
[0]     path: '/',
[0]     secure: true,
[0]     sameSite: 'lax',
[0]     expires: 1970-01-01T00:43:12.000Z
[0]   }
[0] }

but in the browser, nothing is set.

Attempt with Server action

// actions/auth.ts

'use server';

import { cookies } from 'next/headers';
import { AUTH_COOKIES, MOCK_AUTH_COOKIES } from '@helper/constants';
import { CONFIG } from '@/config/config';
import { getCsrfTokenFromSignedCookie } from '@/utilities/auth/auth';
import { ResponseCookie } from 'next/dist/compiled/@edge-runtime/cookies';


export type AuthCookies = {
  tokenAccess?: string;
  tokenRefresh?: string;
  tokenCsrf?: string;
};

const mockAuthCookieConfig: Cookies.CookieAttributes = {
  domain: `.localhost`,
  path: '/',
  secure: true,
  sameSite: 'lax' // NOTE: To avoid URL for breaking when clicked from an email.
};

/*
 * This function sets the mock auth cookies in the browser.
 * It should only be used for development_local environment.
 * */
export async function setMockAuthCookiesIfApplicable({ tokenAccess, tokenRefresh, tokenCsrf }: AuthCookies) {
  const cookieStore = cookies()

  if (!CONFIG.auth.mock || CONFIG.environment !== 'development_local') {
    return;
  }

  if (CONFIG.auth.mock && tokenAccess && tokenRefresh && tokenCsrf) {
    console.log('Setting mock auth cookies in the browser');
    const oneMonth = 24 * 60 * 60 * 30;

    const cookiePairs = {
      [MOCK_AUTH_COOKIES.MOCK_CSRF_TOKEN]: tokenCsrf,
      [MOCK_AUTH_COOKIES.MOCK_REFRESH_TOKEN]: tokenRefresh,
      [MOCK_AUTH_COOKIES.MOCK_ACCESS_TOKEN]: tokenAccess
    };

    Object.entries(cookiePairs).forEach(([k, v]) => {
      cookieStore.set(k, v, {
        ...mockAuthCookieConfig,
        expires: oneMonth,
      } as ResponseCookie);
    })
  }
}
// app/auth/page.tsx

export default async function AuthPage({ searchParams }: AuthPageParams) {

  await setMockAuthCookiesIfApplicable(searchParams);

  redirect(searchParams.redirectTo || '/');
}

Now, when visiting the URL, it just throw straight error
image

What am I missing here?

Expected Behavior

cookie.set should work as doc described.

To Reproduce

The code to reproduce is included in the description. I have nothing special included in the layout.tsx file.

@t18n t18n added the examples Issue/PR related to examples label Sep 13, 2024
@t18n
Copy link
Author

t18n commented Sep 13, 2024

Not sure why it added the examples label there. I think I understood the "Report an issue with an example" option wrongly.

@wyattjoh
Copy link
Member

Setting secure: true will have the browser reject setting the cookie when it's not in a secure context. Can you remove that option and see if it resolves? Also try looking at the response headers on the POST request, I think the Set-Cookie header is indeed set.

@t18n
Copy link
Author

t18n commented Sep 15, 2024

@wyattjoh thank you for the suggestion. The secure: true did not fix it but when I added 'use client' to make the call on the front-end, the cookies are set correctly with or without secure flag

@t18n
Copy link
Author

t18n commented Sep 16, 2024

@wyattjoh Does it relate to your comment #46255 (comment)?

@t18n
Copy link
Author

t18n commented Sep 16, 2024

When I tried to set the cookie with Route Handler, calling the API endpoint from the server side, it doesn't work despite the response including the cookies I need to set. However, adding use client on the page and turning it into a client call sets the cookie correctly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
examples Issue/PR related to examples
Projects
None yet
Development

No branches or pull requests

2 participants