Выход NextAuth.js, если токен Apollo GraphQL недействителен или срок его действия истек

Как лучше всего очистить сеанс NextAuth.js при попытке попасть в бэкэнд (Apollo GraphQL), и он возвращает 401, потому что срок действия токена истек или он недействителен?

Я думал о errorLink и signout, но насколько я знаю signout не может использоваться на стороне сервера getServerSideProps, но только на стороне клиента.

Каков рекомендуемый способ сделать это? Есть ли другой способ реализовать промежуточное ПО, которое позаботится об этом сценарии?

Это было бы доказательством концепции errorLink Я пытаюсь реализовать, код застрял в этом if но я не могу использовать signOut()поскольку он доступен только на стороне клиента.

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}` : '' },
      }),
    ]),
  });
}

благодаря

3 ответа

Решение

Как сказали вам Ной и Йог, это невозможно сделать на стороне сервера, поскольку signOutдолжен очистить состояние на стороне клиента. Вот как бы я это сделал:

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}` : '' },
      }),
    ]),
  });
}

Затем в вашем 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 } };
  }
};

Наконец, ваша страница будет выглядеть так:

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);

Sign Out() очищает сеанс, очищая состояние на стороне клиента, здесь вы можете проверить из бэкэнда, существует ли состояние, а если его нет, тогда сделайте что-нибудь вместо 401(Unauthorized). Надеюсь, вы это прочитали: https://next-auth.js.org/getting-started/client#signout

Это зависит от того, какую версию Apollo вы используете, но, предполагая, что вы используете Apollo 3.0 <=, вы можете создать setcontext для своих запросов.

         import { setContext } from '@apollo/client/link/context';

const authLink = setContext((_, { headers }) => {
  const token = session?.accessToken ? session.accessToken : ""
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : "",
    }
  }
});

function createApolloClient(session) {
  return new ApolloClient({
    cache: new InMemoryCache(),
    ssrMode: typeof window === 'undefined',
    link: from([
      authLink,
      errorLink,
      createUploadLink({
        uri: GRAPHQL_URI,
        credentials: 'same-origin'
      }),
    ]),
  });
}

Поскольку signOut() повторно визуализирует ваше приложение, вы можете проверить контекст, чтобы узнать, есть ли у него действительный токен по запросу с вашего сервера. Не тестировал это, но теоретически я могу реализовать это так.

Другие вопросы по тегам