Skip to content

Cookie Headers Not Forwarded Correctly in React Router and Backend Integration #10488

Open
@sidyr6002

Description

@sidyr6002

Reproduction

You can access the backend code: https://github.com/sidyr6002/chat-app-backend,
frontend code: https://github.com/sidyr6002/chat-app-frontend

Frontend Setup

  1. Create an .env file with the following content:
    BACKEND_URL=http://localhost:3000
    SESSION_SECRET=edb906c05d1aa272eca98019cbf637bf9983c043c02b00a5c7a0df5e041b0447
  2. Run the following commands:
    pnpm install
    pnpm dev

Backend Setup

  1. Set up the database:
    cd /database
    docker compose up
  2. Create an .env file with the following content:
    DATABASE_URL="postgres://user:password@localhost:5432/chatapp?schema=public"
    JWT_SECRET=string
    JWT_REFRESH_SECRET=string
    JWT_EXPIRATION="15m"
  3. Run the following commands:
    pnpm install
    pnpm start:dev

System Info

System:
    OS: Linux 6.11 Ubuntu 24.04.2 LTS 24.04.2 LTS (Noble Numbat)
    CPU: (8) x64 Intel(R) Core(TM) i5-8265U CPU @ 1.60GHz
    Memory: 1.37 GB / 7.61 GB
    Container: Yes
    Shell: 5.2.21 - /usr/bin/bash
  Binaries:
    Node: 20.15.1 - ~/.nvm/versions/node/v20.15.1/bin/node
    Yarn: 1.22.22 - ~/.nvm/versions/node/v20.15.1/bin/yarn
    npm: 10.8.2 - ~/.nvm/versions/node/v20.15.1/bin/npm
    pnpm: 9.15.3 - ~/.local/share/pnpm/pnpm
    bun: 1.1.10 - ~/.local/share/reflex/bun/bin/bun
  Browsers:
    Chrome: 133.0.6943.98

Used Package Manager

pnpm

Expected Behavior

The headers set in the backend should be reflected in the browser. The backend sets cookies (myRefreshToken and accessToken) during the sign-in process.

Backend Code (NestJS):

@ApiOkResponse({ type: AccessTokenDto })
@Post('signin')
async login(
    @Body() signInDto: SingInDto,
    @Res({ passthrough: true }) res: Response,
): Promise<AccessTokenDto> {
    const { accessToken, refreshToken } = await this.authService.signIn(
        signInDto.email,
        signInDto.password,
    );
    res.cookie('myRefreshToken', refreshToken, {
        httpOnly: true,
        secure: false,
        sameSite: 'lax',
        maxAge: 7 * 24 * 60 * 60 * 1000,
    });
    return { accessToken };
}

Frontend Code (React Router Action):

export const action: ActionFunction = async ({ request }) => {
    const formData = await request.formData();
    const email = formData.get('email');
    const password = formData.get('password');
    const validation = signInSchema.safeParse({
        email,
        password,
    });
    if (!validation.success) {
        return Response.json(
            { errors: validation.error.format() },
            { status: 400 }
        );
    }
    try {
        const backendUrl = process.env.BACKEND_URL || '';
        const response = await axios.post(
            `${backendUrl}/auth/signin`,
            { email, password },
            {
                headers: { 'Content-Type': 'application/json' },
                withCredentials: true,
            }
        );
        const { accessToken } = response.data;
        console.log("accessToken", accessToken);
        console.log('Response headers:', response.headers);

        const session = await getSession(request.headers.get('Cookie'));
        session.set('accessToken', accessToken);
        const sessionCookie = await commitSession(session);

        return redirect("/", {
            headers: {
                "Set-Cookie": sessionCookie,
            },
        });
    } catch (error) {
        console.error('Sign-in error:', error);
        return Response.json(
            { error: "Invalid credentials or server error." },
            { status: 401 }
        );
    }
};

The signin API sets the myRefreshToken cookie in the backend and sends the accessToken to the frontend. Both cookies should appear in the browser.

Actual Behavior

Only one cookie (accessToken) is being set in the browser. The myRefreshToken cookie, which is set by the backend, does not appear in the browser.

Upon inspecting the network request in the browser's developer tools, the response headers from the backend include both cookies:

Set-Cookie: myRefreshToken=...; HttpOnly; Path=/; SameSite=Lax; Max-Age=604800
Set-Cookie: accessToken=...; Path=/; HttpOnly

However, in the browser's "Application" tab, only the accessToken cookie is visible.

Image

Additional Information

  • The withCredentials: true option is set in the Axios request.
  • The backend's CORS configuration allows credentials.
  • The SameSite attribute for the myRefreshToken cookie is set to lax.

Observation:

The response headers returned by the backend during the sign-in process contain the myRefreshToken cookies, as confirmed by logging console.log('Response headers:', response.headers);. While the headers appear correct in the network response and logs, the myRefreshToken cookie is not being set in the browser, even though it is present in the Set-Cookie header of the response.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions