Access Cookies in TRPC fetch handler

31 Views Asked by At

I am building Token Based Authentication in my TRPC Nextjs application. I am storing the refresh token in the users cookies. In case a request fails because the access token is expired, i want to access my API route to generate a new access token.I can't wrap my head around how to access the cookies (in my case the refreshToken) in my TRPC fetch handler that runs on each request. (It obviously runs clientside)

import {  httpBatchLink, loggerLink } from "@trpc/client";
import { createTRPCNext } from "@trpc/next";
import { type inferRouterInputs, type inferRouterOutputs } from "@trpc/server";
import superjson from "superjson";
import { type AppRouter } from "@/server/api/root";

export let token: string;

export const setToken = (newToken: string) => {
  token = newToken;
};

{... ...}

export const api = createTRPCNext<AppRouter>({
  config({ctx}) {
    return {
      links: [
        loggerLink({
          ...
        }),

        httpBatchLink({
          transformer: superjson,
          url: `${getBaseUrl()}/api/trpc`,
          headers: ({
          }) => {
            const newHeaders = new Headers();
          
            if(token) newHeaders.set("Authorization", `Bearer ${token}`);
            return newHeaders;
          },
          fetch(url, options) {

            //I need to access the cookies here
            //Ctx is undefined
            
            return fetch(url, {
              ...options,
              credentials: "include",
            });
          },

        }),
        
      ],
      
    };
  },
  ssr: false,
  transformer: superjson,
   
});
{... ...}

I tried using a package like js-cookie but this did not work since the code does not run in an actual React component. I also tried accessing the my ctx containing the headers passed down in the config({ctx}) but it is always undefined

1

There are 1 best solutions below

0
Arash Jahan Bakhshan On

In the page router, we can access the headers only in the getServerSideProps or getInitialProps. And if you want to access the headers using tRPC, you should enable its ssr flag as described in the official document: https://trpc.io/docs/client/nextjs/ssr

Which will be like this:

import { httpBatchLink } from '@trpc/client';
import { createTRPCNext } from '@trpc/next';
import { ssrPrepass } from '@trpc/next/ssrPrepass';
import superjson from 'superjson';
import type { AppRouter } from './api/trpc/[trpc]';

export const trpc = createTRPCNext<AppRouter>({
  ssr: true, // enabled it here
  ssrPrepass,
  config(opts) {
    const { ctx } = opts;
    if (typeof window !== 'undefined') {
      // during client requests
      return {
        links: [
          httpBatchLink({
            url: '/api/trpc',
          }),
        ],
      };
    }

    return {
      links: [
        httpBatchLink({
          url: `${getBaseUrl()}/api/trpc`,
          headers() {
            // access headers here:
            if (!ctx?.req?.headers) {
              return {};
            }
            // To use SSR properly, you need to forward client headers to the server
            // This is so you can pass through things like cookies when we're server-side rendering
            return {
              cookie: ctx.req.headers.cookie,
            };
          },
        }),
      ],
    };
  },
});

However, it's also been said:

When you enable SSR, tRPC will use getInitialProps to prefetch all queries on the server. This results in problems like this when you use getServerSideProps, and solving it is out of our hands.

Alternatively, you can leave SSR disabled (the default) and use Server-Side Helpers to prefetch queries in getStaticProps or getServerSideProps.

So the recommended solution is to use the Server-Side Helpers which can be done in getServerSideProps function (example from official document https://trpc.io/docs/client/nextjs/server-side-helpers#nextjs-example):

import { createServerSideHelpers } from '@trpc/react-query/server';
import { GetServerSidePropsContext, InferGetServerSidePropsType } from 'next';
import { appRouter } from 'server/routers/_app';
import superjson from 'superjson';
import { trpc } from 'utils/trpc';

export async function getServerSideProps(
  context: GetServerSidePropsContext<{ id: string }>,
) {
  const helpers = createServerSideHelpers({
    router: appRouter,
    ctx: {},
    transformer: superjson,
  });
  const id = context.params?.id as string;

  /*
   * Prefetching the `post.byId` query.
   * `prefetch` does not return the result and never throws - if you need that behavior, use `fetch` instead.
   */
  await helpers.post.byId.prefetch({ id });

  // Make sure to return { props: { trpcState: helpers.dehydrate() } }
  return {
    props: {
      trpcState: helpers.dehydrate(),
      id,
    },
  };
}

export default function PostViewPage(
  props: InferGetServerSidePropsType<typeof getServerSideProps>,
) {
  const { id } = props;
  const postQuery = trpc.post.byId.useQuery({ id });

  if (postQuery.status !== 'success') {
    // won't happen since the query has been prefetched
    return <>Loading...</>;
  }

  const { data } = postQuery;

  return (
    <>
      <h1>{data.title}</h1>
      <em>Created {data.createdAt.toLocaleDateString()}</em>
      <p>{data.text}</p>
      <h2>Raw data:</h2>
      <pre>{JSON.stringify(data, null, 4)}</pre>
    </>
  );
}