Description
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
- Create an
.env
file with the following content:BACKEND_URL=http://localhost:3000 SESSION_SECRET=edb906c05d1aa272eca98019cbf637bf9983c043c02b00a5c7a0df5e041b0447
- Run the following commands:
pnpm install pnpm dev
Backend Setup
- Set up the database:
cd /database docker compose up
- 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"
- 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.
Additional Information
- The
withCredentials: true
option is set in the Axios request. - The backend's CORS configuration allows credentials.
- The
SameSite
attribute for themyRefreshToken
cookie is set tolax
.
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.