1

对于使用 apollo 客户端的经过身份验证的 gql 请求,我无法正确地将我的 jwt 令牌从我的 cookie 设置为我的标头。

我相信问题出在我的 withApollo.js 文件上,该文件将 App 组件包装在 _app.js 上。此文件的格式基于 wes bos 高级 react nextjs graphql 课程。发生的事情是 nextauth 将 JWT 保存为 cookie,然后我可以使用自定义正则表达式函数从该 cookie 中获取 JWT。然后我尝试将此令牌值设置为授权承载标头。问题是,在第一次加载带有需要 jwt 令牌的 gql 查询的页面时,我收到错误“无法读取未定义的属性 'cookie'”。但是,如果我点击浏览器刷新,那么它会突然起作用,并且令牌已成功设置为标题。

一些研究使我添加了一个 setcontext 链接,因此我尝试执行此操作。我尝试异步等待设置令牌值,但这似乎没有帮助。似乎标题在刷新之前不想设置。

lib/withData.js

import { ApolloClient, ApolloLink, InMemoryCache } from '@apollo/client';
import { onError } from '@apollo/link-error';
import { getDataFromTree } from '@apollo/react-ssr';
import { createUploadLink } from 'apollo-upload-client';
import withApollo from 'next-with-apollo';
import { setContext } from 'apollo-link-context';
import { endpoint, prodEndpoint } from '../config';
import paginationField from './paginationField';

const getCookieValue = (name, cookie) =>
  cookie.match(`(^|;)\\s*${name}\\s*=\\s*([^;]+)`)?.pop() || '';
let token;

function createClient(props) {
  const { initialState, headers, ctx } = props;
  console.log({ headers });
  // console.log({ ctx });
  return new ApolloClient({
    link: ApolloLink.from([
      onError(({ graphQLErrors, networkError }) => {
        if (graphQLErrors)
          graphQLErrors.forEach(({ message, locations, path }) =>
            console.log(
              `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
            )
          );
        if (networkError)
          console.log(
            `[Network error]: ${networkError}. Backend is unreachable. Is it running?`
          );
      }),
      setContext(async (request, previousContext) => {
        token = await getCookieValue('token', headers.cookie);
        return {
          headers: {
            authorization: token ? `Bearer ${token}` : '',
          },
        };
      }),
      createUploadLink({
        uri: process.env.NODE_ENV === 'development' ? endpoint : prodEndpoint,
        fetchOptions: {
          credentials: 'include',
        },
        headers,
      }),
    ]),
    cache: new InMemoryCache({
      typePolicies: {
        Query: {
          fields: {
            // TODO: We will add this together!
            // allProducts: paginationField(),
          },
        },
      },
    }).restore(initialState || {}),
  });
}

export default withApollo(createClient, { getDataFromTree });

页面/_app.js

import { ApolloProvider } from '@apollo/client';
import NProgress from 'nprogress';
import Router from 'next/router';
import { Provider, getSession } from 'next-auth/client';
import { CookiesProvider } from 'react-cookie';
import nookies, { parseCookies } from 'nookies';
import Page from '../components/Page';
import '../components/styles/nprogress.css';
import withData from '../lib/withData';

Router.events.on('routeChangeStart', () => NProgress.start());
Router.events.on('routeChangeComplete', () => NProgress.done());
Router.events.on('routeChangeError', () => NProgress.done());

function MyApp({ Component, pageProps, apollo, user }) {
  return (
    <Provider session={pageProps.session}>
      <ApolloProvider client={apollo}>
        <Page>
          <Component {...pageProps} {...user} />
        </Page>
      </ApolloProvider>
    </Provider>
  );
}

MyApp.getInitialProps = async function ({ Component, ctx }) {
  let pageProps = {};
  if (Component.getInitialProps) {
    pageProps = await Component.getInitialProps(ctx);
  }
  pageProps.query = ctx.query;

  const user = {};

  const { req } = ctx;
  const session = await getSession({ req });

  if (session) {
    user.email = session.user.email;
    user.id = session.user.id;
    user.isUser = !!session;

    // Set
    nookies.set(ctx, 'token', session.accessToken, {
      maxAge: 30 * 24 * 60 * 60,
      path: '/',
    });
  }

  return {
    pageProps,
    user: user || null,
  };
};

export default withData(MyApp);

api/auth/[...nextAuth.js]

import NextAuth from 'next-auth';
import Providers from 'next-auth/providers';
import axios from 'axios';

const providers = [
  Providers.Google({
    clientId: process.env.GOOGLE_CLIENT_ID,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET,
  }),
  Providers.Credentials({
    name: 'Credentials',
    credentials: {
      username: { label: 'Username', type: 'text', placeholder: 'jsmith' },
      password: { label: 'Password', type: 'password' },
    },
    authorize: async (credentials) => {
      const user = await axios
        .post('http://localhost:1337/auth/local', {
          identifier: credentials.username,
          password: credentials.password,
        })
        .then((res) => {
          res.data.user.token = res.data.jwt;
          return res.data.user;
        }) // define user as res.data.user (will be referenced in callbacks)
        .catch((error) => {
          console.log('An error occurred:', error);
        });
      if (user) {
        return user;
      }
      return null;
    },
  }),
];

const callbacks = {
  // Getting the JWT token from API response
  async jwt(token, user, account, profile, isNewUser) {
    // WRITE TO TOKEN (from above sources)
    if (user) {
      const provider = account.provider || user.provider || null;
      let response;
      let data;
      switch (provider) {
        case 'google':
          response = await fetch(
            `${process.env.NEXT_PUBLIC_API_URL}/auth/google/callback?access_token=${account?.accessToken}`
          );
          data = await response.json();
          if (data) {
            token.accessToken = data.jwt;
            token.id = data.user._id;
          } else {
            console.log('ERROR No data');
          }
          break;

        case 'local':
          response = await fetch(
            `${process.env.NEXT_PUBLIC_API_URL}/auth/local/callback?access_token=${account?.accessToken}`
          );
          data = await response.json();
          token.accessToken = user.token;
          token.id = user.id;
          break;

        default:
          console.log(`ERROR: Provider value is ${provider}`);
          break;
      }
    }

    return token;
  },

  async session(session, token) {
    // WRITE TO SESSION (from token)
    // console.log(token);

    session.accessToken = token.accessToken;
    session.user.id = token.id;

    return session;
  },
  redirect: async (url, baseUrl) => baseUrl,
};

const sessionPreferences = {
  session: {
    jwt: true,
  },
};

const options = {
  providers,
  callbacks,
  sessionPreferences,
};

export default (req, res) => NextAuth(req, res, options);

4

0 回答 0