Search code examples
javascriptreactjstypescriptnext.jstrpc.io

Create T3 App Redirect inside a TRPC middleware if user is not signed


How can I trigger a redirect on the server side if a signed in user has not completed their profile page

const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
  if (!ctx.session || !ctx.session.user) {
    throw new TRPCError({ code: "UNAUTHORIZED" });
  }

  // redirect to profile page if user has not completed profile


  return next({
    ctx: {
      // infers the `session` as non-nullable
      session: { ...ctx.session, user: ctx.session.user },
    },
  });
});

Solution

  • This is not currently possible in the way you are describing to the best of my knowledge.

    Here are some alternatives that might be helpful:

    In getServerSideProps

    this only works if you want to redirect before the initial page load. You could also create a wrapper around gSSP to make this more DRY if you're going to use it on a lot of pages.

    import { type GetServerSidePropsContext } from "next";
    import { getServerAuthSession } from "../server/auth";
    
    export async function getServerSideProps(ctx: GetServerSidePropsContext) {
      const session = await getServerAuthSession(ctx);
    
      if (!session) {
        return {
          redirect: {
            destination: "/",
            permanent: false,
          },
        };
      }
    
      return {
        props: {},
      };
    }
    
    export default function AuthedPage() {
      return <div>Authed</div>;
    }
    

    As part of a query or mutation clientside

    this is useful for a query or mutation that is only fired after the page has loaded. Again this is a very simple example and could be DRYed, probably the easiest way would be to extract into a custom hook.

    import { useRouter } from "next/router";
    import { api } from "../utils/api";
    
    export default function AuthedPage() {
      const router = useRouter();
    
      // `authedHello` is the example Create T3 App "hello" procedure
      // but as a protectedProcedure, ie throws "UNAUTHORIZED" if no session.
      // Replace this with a middleware that throws on whatever condition you need it to.
      const authedHello = api.example.protectedHello.useQuery(
        { text: "world" },
        {
          retry: (_count, err) => {
            // `onError` only runs once React Query stops retrying
            if (err.data?.code === "UNAUTHORIZED") {
              return false;
            }
            return true;
          },
          onError: (err) => {
            if (err.data?.code === "UNAUTHORIZED") {
              void router.push("/");
            }
          },
        }
      );
    
      return (
        <div>
          <h1>Authed Page</h1>
          <p>{authedHello.data?.greeting}</p>
        </div>
      );
    }
    

    Using Next.js middleware

    This is easy to apply to a bunch of routes using the matcher, but it falls a bit outside of T3 conventions.

    // pages/middleware.ts
    import { NextResponse } from "next/server";
    import { getServerSession } from "next-auth";
    import { authOptions } from "../server/auth";
    import type { NextApiRequest, NextApiResponse } from "next";
    
    export async function middleware(req: NextApiRequest, res: NextApiResponse) {
      const session = await getServerSession(req, res, authOptions);
      if (!session?.user) {
        return NextResponse.redirect(new URL("/", req.url));
      }
    }
    
    export const config = {
      matcher: ["/protectedPage", "/anotherProtectedPage"],
    };
    

    Using require in next-auth's useSession

    this is useful if you want to guard a page but can't use getServerSideProps. It doesn't quite solve your specific problem, but might be useful to other people who find this. See: https://next-auth.js.org/getting-started/client#require-session