Next.js server action: axios not using credential that has been set in browser by Nest.js server
Answered
Minskin posted this in #help-forum
MinskinOP
So I am new to next.js 14, I am building an application that use Next.js in the frontend and Nest.js in the backend. After developing the backend solely in Nest.js and testing it via postman, it never has problem with cookie, as postman can receive it and set it to its header and use it for future request. However when I am trying to fetch data using Next.js server action, the cookie that has been set in the browser is not used by Axios, hence my server denying resources to client.
//auth.controller.ts (nest.js server)
@Post('login')
async signIn(@Request() req, @Res() res: Response) {
const { userID, accessToken, refreshToken } = await this.authService.login(
req.user,
);
res
.cookie('access_token', accessToken, {
httpOnly: true,
secure: true,
sameSite: 'lax',
expires: new Date(Date.now() + 1 * 12 * 60 * 1000),
})
.send({ status: 'ok' });
}
//page.tsx (next.js)
import axios from 'axios';
import { redirect } from 'next/navigation';
export default async function Page() {
async function postLogin(input: FormData) {
'use server';
let body = {
email: input.get('email'),
password: input.get('password'),
};
const { data }: any = await axios.post(
'http://127.0.0.1:8080/auth/login',
body,
);
redirect('/users')
}
return (
<div className='h-screen w-screen bg-slate-300 flex flex-col align-middle justify-center content-center'>
<form action={postLogin}>
<input name='email' type='email' placeholder='example: john@mail.com' />
<input
name='password'
type='password'
placeholder='8 characters minimum'
/>
<button>login</button>
</form>
</div>
);
}
Answered by Rafael Almeida
when you fire the request from the CLIENT, the browser will automatically add the cookies into this request. then in the next server you need to grab the cookie from the request using
or if you dont mind sending the entire client headers:
next/cookies
and then add this cookie in the fetch request to your nest server. like this:const token = cookies().get('token')
const req = await fetch('...', {
headers: {
Cookie: `token=${token}`
}
})
or if you dont mind sending the entire client headers:
const req = await fetch('...', { headers: headers() })
26 Replies
MinskinOP
//page.tsx (/users in next.js)
import axios from 'axios';
import { cookies } from 'next/headers';
export default async function () {
async function getUserList() {
'use server';
const response = await axios('http://127.0.0.1:8080/users?offset=0&limit=10',{
method:'GET',
withCredentials:true
})
console.log(response);
}
let data = getUserList()
// const userList = data.map((element: any) => {
// return (
// <ul>
// <li>{element.fullName}</li>
// <li>{element.gender}</li>
// </ul>
// );
// });
return <div className='list'>Hello this is rendred from server</div>;
}
cookie extraction from nest.js server doesn't get any cookie
response from nest.js server
@Minskin tsx
//page.tsx (/users in next.js)
import axios from 'axios';
import { cookies } from 'next/headers';
export default async function () {
async function getUserList() {
'use server';
const response = await axios('http://127.0.0.1:8080/users?offset=0&limit=10',{
method:'GET',
withCredentials:true
})
console.log(response);
}
let data = getUserList()
// const userList = data.map((element: any) => {
// return (
// <ul>
// <li>{element.fullName}</li>
// <li>{element.gender}</li>
// </ul>
// );
// });
return <div className='list'>Hello this is rendred from server</div>;
}
it's because you are never setting the cookies in the request from your Next.js server to the Nest.js server. cookies only exist in the client so
withCredentials:true
does nothing in the serveryou need to read the cookies from the client using
next/cookies
and add them manually in your request to the Nest.js serverMinskinOP
what do you mean by add the cookie manually? isn't cookie supposed to be automatic..?
only on the browser, you are making the fetch call in your server. the server doesn't have any cookies info, so thats why the credentials flag doesn't do anything
the easiest way to forward the cookies in the server fetch call is to replicate the headers from the request:
fetch(..., { header: headers() })
but I prefer to only forward the cookies to keep the request cleaner
MinskinOP
so I have looked around google about what you said earlier, it does make sense since the server action is separated from the browser, it shouldn't have had any access to the browser cookie. So how do I forward the cookie to the request then?
I can only find a stackoverflow answer that add a cookie to a function but they didn't give away how the function load that cookie to the request...
@Minskin I can only find a stackoverflow answer that add a cookie to a function but they didn't give away how the function load that cookie to the request...
when you fire the request from the CLIENT, the browser will automatically add the cookies into this request. then in the next server you need to grab the cookie from the request using
or if you dont mind sending the entire client headers:
next/cookies
and then add this cookie in the fetch request to your nest server. like this:const token = cookies().get('token')
const req = await fetch('...', {
headers: {
Cookie: `token=${token}`
}
})
or if you dont mind sending the entire client headers:
const req = await fetch('...', { headers: headers() })
Answer
this is written with fetch but you can adapt it to axios, it should be very similar
MinskinOP
alright, I will implement it right away
wish me luck
MinskinOP
async function getUsers() {
'use server';
const token = cookies().getAll('access_token')[0];
const response = await fetch(
'http://127.0.0.1:8080/users?offset=0&limit=10',
{
method: 'GET',
headers: {
Cookie: `access_token=${token.value}`
},
},
);
return response;
}
let data = await getUsers();
{
aborted: false,
rangeRequested: false,
timingAllowPassed: true,
requestIncludesCredentials: true,
type: 'default',
status: 200,
timingInfo: {
startTime: 177798.5435,
redirectStartTime: 0,
redirectEndTime: 0,
postRedirectStartTime: 177798.5435,
finalServiceWorkerStartTime: 0,
finalNetworkResponseStartTime: 0,
finalNetworkRequestStartTime: 0,
endTime: 0,
encodedBodySize: 2,
decodedBodySize: 2,
finalConnectionTimingInfo: null
},
cacheState: '',
statusText: 'OK',
headersList: HeadersList {
cookies: null,
[Symbol(headers map)]: [Map],
[Symbol(headers map sorted)]: null
},
urlList: [ URL {} ],
body: { stream: undefined, length: undefined, source: undefined }
},
[Symbol(headers)]: HeadersList {
cookies: null,
[Symbol(headers map)]: Map(9) {
'x-powered-by' => [Object],
'access-control-allow-origin' => [Object],
'access-control-allow-credentials' => [Object],
'content-type' => [Object],
'content-length' => [Object],
'etag' => [Object],
'date' => [Object],
'connection' => [Object],
'keep-alive' => [Object]
},
[Symbol(headers map sorted)]: null
}
}
got status 200, but there is no data...
token also extracted successfully in the nestjs
MinskinOP
anw thank you for your support and answer
will look into the solution to another problem later
but your solution does work and it give me status 200, just need to figured out why it doesn't return data as expected
okay solved using axios, rather than fetch
thank you so much @Rafael Almeida
no problem, glad you got it working