Warm tip: This article is reproduced from serverfault.com, please click

NextAuth.js signout if Apollo GraphQL token is invalid or it has expired

发布于 2020-11-22 11:35:04

What would it be the best way to clear the NextAuth.js session when trying to hit the backend (Apollo GraphQL) and it returns a 401 because the token has expired or is invalid?

I thought about an errorLink and signout, but as far as I know signout cannot be used server side at getServerSideProps, but only client-side.

What is the recommended way to do so? Is there any other way to implement a middleware to take care of that scenario?

This would be the proof of concepto of the errorLink I'm trying to implement, the code is caught in that if but I can't use signOut() as it's only available client-side.

const errorLink = onError(({ graphQLErrors }) => {
   if (graphQLErrors?.[0]?.message === 'Unauthenticated.') {
    // signOut();
  }
});

function createApolloClient(session) {
  return new ApolloClient({
    cache: new InMemoryCache(),
    ssrMode: typeof window === 'undefined',
    link: from([
      errorLink,
      createUploadLink({
        uri: GRAPHQL_URI,
        credentials: 'same-origin',
        headers: { Authorization: session?.accessToken ? `Bearer ${session.accessToken}` : '' },
      }),
    ]),
  });
}

Thanks

Questioner
VanPersie
Viewed
0
blacksoul 2020-12-01 17:13:11

As Noah and Yog told you, there is no way to do it server-side, as signOut must clear the state client-side. So this is how I would do it:

function createApolloClient(session) {
  return new ApolloClient({
    cache: new InMemoryCache(),
    ssrMode: typeof window === 'undefined',
    link: from([
      createUploadLink({
        uri: GRAPHQL_URI,
        credentials: 'same-origin',
        headers: { Authorization: session?.accessToken ? `Bearer ${session.accessToken}` : '' },
      }),
    ]),
  });
}

Then, in your getServerSiderProps:

export const fetchServerSideProps = (
  initializeApollo: (context: SessionBase | null) => ApolloClient<NormalizedCacheObject>
): GetServerSideProps => async (context) => {
  const session = await getSession(context);
  const apolloClient = initializeApollo(session);
  try {
    // Fetch anything from GraphQL here

    return {
      props: {
        // add your required page props here
        session,
      },
    };
  } catch {
    // Token invalid or expired error (401) caught here, so let's handle this client-side.
    return { props: { session: null } };
  }
};

Finally, your page would look like:

const withAuth = <P extends { session: Session | null }>(Page: NextPage<P>) => (props: P): JSX.Element | null => {
  if (!props.session) {
    // Clear session and redirect to login
    signOut({ callbackUrl: '/login' });
    return null;
  }

  return <Page {...props} />;
};

const Page: NextPage<P> = (props) => (
  <p>This should be shown ONLY if the user is logged in</p>
); 

export default withAuth(LoggedInPage);