Apollo订阅JWT身份验证

时间:2020-02-03 23:08:37

标签: authentication jwt apollo apollo-client apollo-server

我正在使用Robin Wieruch的完整堆栈样板,但是缺少订阅的身份验证。它使用JWT令牌进行会话,并且对于http正常工作,但是完全缺少ws auth。

我还需要为订阅传递用户低谷上下文,我需要订阅解析器中的会话信息,才能确定我是否应该触发订阅的天气。

我确实搜索了Apollo文档,我看到我应该使用onConnect: (connectionParams, webSocket, context)函数,但是没有完整功能的全栈示例,我不确定如何从客户端传递JWT以便能够在webSocket对象中获取它。

这是我到目前为止所拥有的:

服务器:

import express from 'express';
import {
  ApolloServer,
  AuthenticationError,
} from 'apollo-server-express';

const app = express();

app.use(cors());

const getMe = async req => {
  const token = req.headers['x-token'];

  if (token) {
    try {
      return await jwt.verify(token, process.env.SECRET);
    } catch (e) {
      throw new AuthenticationError(
        'Your session expired. Sign in again.',
      );
    }
  }
};

const server = new ApolloServer({
  introspection: true,
  typeDefs: schema,
  resolvers,
  subscriptions: {
    onConnect: (connectionParams, webSocket, context) => {
      console.log(webSocket);
    },
  },
  context: async ({ req, connection }) => {
    // subscriptions
    if (connection) {
      return {
        // how to pass me here as well?
        models,
      };
    }

    // mutations and queries
    if (req) {
      const me = await getMe(req);

      return {
        models,
        me,
        secret: process.env.SECRET,
      };
    }
  },
});

server.applyMiddleware({ app, path: '/graphql' });

const httpServer = http.createServer(app);
server.installSubscriptionHandlers(httpServer); 

const isTest = !!process.env.TEST_DATABASE_URL;
const isProduction = process.env.NODE_ENV === 'production';
const port = process.env.PORT || 8000;


httpServer.listen({ port }, () => {
    console.log(`Apollo Server on http://localhost:${port}/graphql`);
});

客户:


const httpLink = createUploadLink({
  uri: 'http://localhost:8000/graphql',
  fetch: customFetch,
});

const wsLink = new WebSocketLink({
  uri: `ws://localhost:8000/graphql`,
  options: {
    reconnect: true,
  },
});

const terminatingLink = split(
  ({ query }) => {
    const { kind, operation } = getMainDefinition(query);
    return (
      kind === 'OperationDefinition' && operation === 'subscription'
    );
  },
  wsLink,
  httpLink,
);

const authLink = new ApolloLink((operation, forward) => {
  operation.setContext(({ headers = {} }) => {
    const token = localStorage.getItem('token');

    if (token) {
      headers = { ...headers, 'x-token': token };
    }

    return { headers };
  });

  return forward(operation);
});

const errorLink = onError(({ graphQLErrors, networkError }) => {
  if (graphQLErrors) {
    graphQLErrors.forEach(({ message, locations, path }) => {
      console.log('GraphQL error', message);

      if (message === 'UNAUTHENTICATED') {
        signOut(client);
      }
    });
  }

  if (networkError) {
    console.log('Network error', networkError);

    if (networkError.statusCode === 401) {
      signOut(client);
    }
  }
});

const link = ApolloLink.from([authLink, errorLink, terminatingLink]);

const cache = new InMemoryCache();

const client = new ApolloClient({
  link,
  cache,
  resolvers,
  typeDefs,
});

1 个答案:

答案 0 :(得分:1)

您需要使用connectionParams从客户端设置JWT。下面是使用角度框架的代码段:

Func query

在服务器端,使用Func<PropertyInfo,bool> query = q => q.PropertyType.IsClass && // Check if property is a class q.CanWrite && // Check if property is not readOnly q.PropertyType != typeof(string) && // Check if property is not string q.PropertyType.GetInterfaces() // Check what classes should be initialize .Any(i => i.Name == nameof(IInitializable) ); ... public interface IInitializable{} public class Envelope : IInitializable { ..... 事件处理程序来处理JWT是正确的。例如

    const WS_URI = `wss://${environment.HOST}:${environment.PORT}${
      environment.WS_PATH
    }`;

    const wsClient = subscriptionService.getWSClient(WS_URI, {
      lazy: true,
      // When connectionParams is a function, it gets evaluated before each connection.
      connectionParams: () => {
        return {
          token: `Bearer ${authService.getJwt()}`
        };
      },
      reconnect: true,
      reconnectionAttempts: 5,
      connectionCallback: (error: Error[]) => {
        if (error) {
          console.log(error);
        }

        console.log('connectionCallback');
      },
      inactivityTimeout: 1000
    });

    const wsLink = new WebSocketLink(wsClient);

服务器端:https://github.com/mrdulin/apollo-graphql-tutorial/blob/master/src/subscriptions/server.ts#L72

客户端:https://github.com/mrdulin/angular-apollo-starter/blob/master/src/app/graphql/graphql.module.ts#L38