Receive cookies in Server Actions
Unanswered
Polar bear posted this in #help-forum
Polar bearOP
Hello there, I have a difficult issue that I can't solve on my own.
I am trying to fetch the cookies inside a server action within
The idea was to include the
Is there any way to read the browser cookies inside the server actions, without a further implementation of passing an argument to my client.ts and create a client in every component that I need it in?
Thank you in advance.
I am trying to fetch the cookies inside a server action within
pages/api/auth/client.ts. This is where I create the ApolloClient:import {
ApolloClient,
ApolloLink,
HttpLink,
InMemoryCache,
} from '@apollo/client'
import { setContext } from '@apollo/client/link/context'
import { getCookie } from 'cookies-next'
import dotenv from 'dotenv'
dotenv.config()
const getBackendUrl = (): string => {
const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL
if (!backendUrl) {
throw new Error('NEXT_PUBLIC_BACKEND_URL is not set')
}
if (process.env.NODE_ENV === 'development') {
return `http://${backendUrl}/graphql`
} else if (process.env.NODE_ENV === 'production') {
return `https://${backendUrl}/graphql`
}
throw new Error('NODE_ENV is not set or not recognized')
}
const httpLink = new HttpLink({
uri: getBackendUrl(),
credentials: 'include',
})
const authLink = setContext((_, { headers }) => {
const token = getCookie('jwt')
return {
headers: {
...headers,
'Authorization': token,
},
}
})
export const client = new ApolloClient({
link: ApolloLink.from([authLink, httpLink]),
cache: new InMemoryCache(),
})The idea was to include the
jwt which is stored inside the browser's cookies in the request header of each query with the client. Although this should work, I always receive undefined if I do console.log(token) inside the authLink method.Is there any way to read the browser cookies inside the server actions, without a further implementation of passing an argument to my client.ts and create a client in every component that I need it in?
Thank you in advance.
28 Replies
Why not use the cookies() function from next? It can be used in server actions @Polar bear
Polar bearOP
Because then I receive this error:
I thought it was mandatory to place the server actions inside the api folder, which is inside the pages folder
Is it okay to put the server actions into a different folder? Because then I would just place them somewhere else.
The issue is that if I do that, I won't be able to send fetch requests to
/api/*and if I move the server actions into a different folder, and use
use server at the top of the file, this is what I receive:Oh this is pages dir? Sorry I'm an app dir guy. Wait for someone pages specific to go through this forum
nope @Polar bear you can get cookies using
https://nextjs.org/docs/app/api-reference/functions/cookies
next/headershttps://nextjs.org/docs/app/api-reference/functions/cookies
oh, is it page router?
Polar bearOP
page router, yes
oh @Polar bear I think you should use a middleware
import { NextRequest, NextResponse } from 'next/server';
export function middleware(req: NextRequest) {
const response = NextResponse.next();
// Check if the cookie already exists
if (!req.cookies.has('your-cookie-name')) {
// Set the cookie if it doesn't exist
response.cookies.set('your-cookie-name', 'your-cookie-value', {
httpOnly: true, // Only accessible by the web server
secure: process.env.NODE_ENV === 'production', // Send only over HTTPS in production
sameSite: 'strict', // Prevents the browser from sending this cookie along with cross-site requests
maxAge: 60 * 60 * 24 * 7, // 1 week
});
}
return response;
}
export const config = {
matcher: '/:path*', // Apply middleware to all routes
};like this?
Hey @Polar bear - have you looked at the https://www.npmjs.com/package/cookies-next package? That has page router support too.
Polar bearOP
yea as said, the issue is that using cookies-next results in
undefined inside the api folder for client.tsRight now I'm actually switching off from apollo and re-structuring the logic of my client, moving to axios and so on
will keep you updated if that solved my problem.
Polar bearOP
I actually would love to not do that, as that creates too much overhead so I would like to ask:
How can I add the browser cookies to the request header of Apollo, if I am hosting frontend and backend in separater docker containers but inside the same network?
Since I can't use server actions, there must be another way...
My bad - I was getting confused between cookies-next and next-cookies. The only thing I notice that differs in your code the the cookies-next page router docs is they suggest using
getCookie('jwt', { req, res }) - perhaps you need to inject the request and response in there and maybe that's why you're getting undefined?But also, moving to axios will probably work too 👍
Polar bearOP
Sooo I solved the issue by understanding the logic behind nextJS without app router. Seems like server actions aren't a thing in the pages router. What happens is: Browser sends request to NextJS -> NextJS sends request to Backend URL via ApolloClient -> Backend responds.
In between there's the ApolloClient, which has nothing to do with the browser, meaning the header isn't the same.
I had to manually assign the cookie header to the ApolloClient so what I did was code a middleware that accepts the request with the cookie header and then it worked
In between there's the ApolloClient, which has nothing to do with the browser, meaning the header isn't the same.
I had to manually assign the cookie header to the ApolloClient so what I did was code a middleware that accepts the request with the cookie header and then it worked
// src/pages/api/auth/client.ts
import {
ApolloClient,
ApolloLink,
HttpLink,
InMemoryCache,
} from '@apollo/client'
import { setContext } from '@apollo/client/link/context'
import dotenv from 'dotenv'
dotenv.config()
const getBackendUrl = (): string => {
const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL
if (!backendUrl) {
throw new Error('NEXT_PUBLIC_BACKEND_URL is not set')
}
if (process.env.NODE_ENV === 'development') {
return `http://${backendUrl}/graphql`
} else if (process.env.NODE_ENV === 'production') {
return `https://${backendUrl}/graphql`
}
throw new Error('NODE_ENV is not set or not recognized')
}
const httpLink = new HttpLink({
uri: getBackendUrl(),
credentials: 'include',
})
export const createClientWithAuth = (headers: Record<string, string> = {}) => {
const authLink = setContext((_, { headers: existingHeaders }) => {
return {
headers: {
...existingHeaders,
...headers,
},
}
})
return new ApolloClient({
link: ApolloLink.from([authLink, httpLink]),
cache: new InMemoryCache(),
})
}example:
import { USER_INFO } from '@/gql'
import { NextApiRequest, NextApiResponse } from 'next'
import { createClientWithAuth } from './client'
const authHandler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'GET') {
try {
const headers = {
cookie: req.headers.cookie || '',
}
const client = createClientWithAuth(headers)
const { data } = await client.query({ query: USER_INFO })
if (data && data.userInfo) {
return res.status(200).json(data.userInfo)
} else {
return res.status(404).json({ error: "Couldn't authenticate user" })
}
} catch (error) {
console.error(error)
return res.status(500).json({ error: 'Internal Server Error' })
}
} else {
res.setHeader('Allow', ['GET'])
res.status(405).end(`Method ${req.method} Not Allowed`)
}
}
export default authHandlerworks like a charm now :) This is how _app.tsx looks with the ApolloProvider:
const App = ({ Component, pageProps }: AppProps) => {
const viewport = useViewport()
const [client, setClient] =
useState<ApolloClient<NormalizedCacheObject> | null>(null)
...
return (
<>
<Head>
<title>myfairs | Management Suite</title>
</Head>
<ApolloProvider client={client}>
...Thank you for the hints and advice
Sweet! Glad you got it working 💪