Sending user credentials when used in Next 13's app dir/react server components

I’ve followed the instructions on the apollo blog to use apollo with NextJS 13’s app directory/react server components. However, the requests don’t contain what they need for my auth system to identify which user is making the requests. It works fine when I force things to run on the client via use client.

Using the credentials: 'include' setting makes no difference when used in the default RSC mode.

Is there a way to make that work?

Hi @viveleroi :wave: I don’t have any suggestions off the top of my head but if you have a repo to link to I’m certain that would help the community assist you. Also CC @patrick91 in case you have any ideas!

thanks for the ping @JeffAuriemma!

Hi @viveleroi! how are you authenticating the user? If you’re using headers/cookies then you can use Next.js to fetch the header and cookies and send them when doing the GraphQL query.

There’s an example of this in the next.js docs, but I’ll copy it here a reference:

import { headers } from 'next/headers';

async function getUser() {
  const headersInstance = headers()
  const authorization = headersInstance.get('authorization')
  // Forward the authorization header
  const res = await fetch('...', {
     headers: { authorization }
  })
  return res.json()
}

export default async function UserPage() {
  const user = await getUser()
  return <h1>{user.name}</h1>
}

You should be able to pass the fetch options to client.query like so:

await client.query({
  context: {
    fetchOptions: {
         headers: { authorization }
    }
  }
});

hope this helps!

1 Like

We’re using Auth0 so in the graphql server, we’re doing this:

export default createYoga<{
  req: NextApiRequest
  res: NextApiResponse
}>({
  context: async function createContext({ req, res }: { req: NextApiRequest; res: NextApiResponse }) {
    const session = await getSession(req, res)

    // If the user is not logged in, return an empty object
    if (!session) {
      return
    }

    return { session }
  },
  schema,
  // Needed to be defined explicitly because our endpoint lives at a different path other than `/graphql`
  graphqlEndpoint: '/api/graphql'
})

The issue is that because ApolloClient is running as a server a component (the default in Next 13’s app directory) it’s not passing whatever cookies/etc even when credentials: 'include' is set. I assume it’s because it’s running on the server and it doesn’t have access to the user info.

On our page, we query like this:

const Dashboard = async (): Promise<JSX.Element> => {
  const client = getClient()
  const { data } = await client.query({ query })

  return (
    <main>
      <h2 className='font-title'>Dashboard</h2>
      <p>{data.greetings}</p>
    </main>
  )
}

And per the blog article, our apollo client code is here:

import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client'

type Client = ApolloClient<any>

let client: Client | null = null

const httpLink = new HttpLink({
  uri: 'http://localhost:3000/api/graphql'
})

export const getClient = (): Client => {
  /* Create a new client if there's no existing one
     or if we are running on the server. */
  if (!client || typeof window === 'undefined') {
    client = new ApolloClient({
      link: httpLink,
      cache: new InMemoryCache(),
      credentials: 'include'
    })
  }

  return client
}

I’ll have to a) do some digging to see which headers/user cookie data need to be sent and b) how to send that using apolloclient, unless what I’ve posted here changes your suggestion

1 Like

For anyone finding this, I was able to get it working by reading the cookie header and forwarding it through the apollo client:

import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client'
import { headers } from 'next/headers'

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Client = ApolloClient<any>

let client: Client | null = null

export const getClient = (): Client => {
  const headersInstance = headers()
  const httpLink = new HttpLink({
    uri: 'http://localhost:3000/api/graphql',
    headers: {
      cookie: headersInstance.get('cookie') ?? ''
    }
  })

  /* Create a new client if there's no existing one
     or if we are running on the server. */
  if (!client || typeof window === 'undefined') {
    client = new ApolloClient({
      link: httpLink,
      cache: new InMemoryCache(),
      credentials: 'include'
    })
  }

  return client
}

thanks @viveleroi ! I was having problems retrieving the next auth context with the apollo client and your solution perfectly solved the problem

1 Like