Getting req.headers.authorization in Mv3?

How do we get req.headers.authorization in Mv3? There’s a forum thread about it here, but it doesn’t seem to apply to Mv3.

Meteor 3 is now using express 4.x through webapp

It works with Mv3:

import { ApolloServer } from "apollo-server-express";
import { WebApp } from "meteor/webapp";
// ...
const apolloServer = new ApolloServer({
  schema,
  context: async ({ req }) => {
    debug("req", req.headers);
    const loginToken = req.headers.authorization;
    const userAgent = req.headers["user-agent"];
   
    return {
      loginToken,
      userAgent,
    };
  },
  cache: new InMemoryLRUCache({
    maxSize: Math.pow(2, 20) * 30, // 30 MB
    ttl: 60 * 5, // 5 minutes (in seconds)
  }),
});

apolloServer.start().then(() => {
  apolloServer.applyMiddleware({
    app: WebApp.connectHandlers,
    cors: true,
    path: "/graphql",
  });
});
1 Like

@minhna

Thanks! Can you show me the matching setup for ApolloClient as well?

Here’s my current ApolloClient setup code:

import React from 'react';
// import { MeteorAccountsLink } from 'meteor/apollo'
import { Hello } from './Hello.jsx';
import { Info } from './Info.jsx';

import { ApolloClient, InMemoryCache, ApolloProvider } from "@apollo/client";
import AccountsPage from "./accounts";

const client = new ApolloClient({
  uri: "http://localhost:4000",
  cache: new InMemoryCache(),
});

export default client;

Could you please post your code so I make sure I’m using client setup that matches with your server setup?

I think WebApp.connectHandlers should be WebApp.handlers instead. Breaking changes | Meteor 3.0 Migration Guide

You’re right. But WebApp.connectHandlers still works as alias of WebApp.handlers

Here is my client setup:

import {
  ApolloProvider,
  ApolloClient,
  InMemoryCache,
  createHttpLink,
  ApolloLink,
} from "@apollo/client";
import { setContext } from "@apollo/client/link/context";
import { onError } from "@apollo/client/link/error";
import { GraphQLWsLink } from "@apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";

import { getMainDefinition } from "@apollo/client/utilities";


// ...

    const wsLink = theWsLinkUrl
      ? new GraphQLWsLink(
          createClient({
            url: theWsLinkUrl,
            connectionParams: () => {
              return {
                authorization: token || "",
              };
            },
            on: {
              error: props => {
                logger("wsLink error", props);
              },
            },
          }),
        )
    : null;

    const httpAuthLink = createHttpLink({
      uri: apiUrl,
      fetchOptions: {
        rejectUnauthorized: false,
      },
    });
    const authLinkContext = setContext((_, { headers }) => {
      // return the headers to the context so httpLink can read them
      return {
        headers: {
          ...headers,
          authorization: token || "",
        },
      };
    });
    const authLink = ApolloLink.from([
      errorLink,
      authLinkContext.concat(httpAuthLink),
    ]);

    const httpAppLink = createHttpLink({
      uri: appApiUrl || "",
    });
    const appLinkContext = setContext((_, { headers }) => {
      // return the headers to the context so httpLink can read them
      return {
        headers: {
          ...headers,
          authorization: token || "",
        },
      };
    });
    const appLink = ApolloLink.from([
      errorLink,
      appLinkContext.concat(httpAppLink),
    ]);
    return new ApolloClient({
      link: ApolloLink.split(
        ({ query }) => {
          const definition = getMainDefinition(query);
          return (
            definition.kind === "OperationDefinition" &&
            definition.operation === "subscription"
          );
        },
        wsLink ? wsLink : appLink,
        ApolloLink.split(
          operation => operation.getContext().clientName === "auth",
          authLink, // <= apollo will send to this if clientName is "auth"
          appLink, // <= otherwise will send to this
        ),
      ),
      cache: new InMemoryCache({
        
      }),
    });
1 Like

For:

const wsLink = theWsLinkUrl
  ? new GraphQLWsLink(

…is theWsLinkUrl stored somewhere for reference here?

It’s a string, websocket server address, e.g: wss://banana.com/graphql

1 Like

Here:

const httpAuthLink = createHttpLink({
    uri: apiUrl,
    fetchOptions: {
        rejectUnauthorized: false,
    },
});

…where does apiUrl come from?

Also, later in the client code:

  • ErrorLink
  • appApiUrl

I can make some guesses, but it will be much faster to ask you.

I really appreciate your help! I’ve been trying to get Apollo working with Mv3 for a week or so.

UPDATE:

Is this in the ballpark?

const url = new URL(window.location.href);
let theWsLinkUrl = `wss://${url.host}/graphql`
let appApiUrl = window.location.href;
let errorLink = window.location.href; //I'm sure this one is specific to how the app wants to handle these errors

those url should be ended with graphql by default. It depends on how you setup your graphql server.

So, with the server setup you provided, would these be good examples?

let theWsLinkUrl = `wss://localhost:3000/graphql`
let appApiUrl = `http://localhost:3000/graphql`;
let errorLink = `http://localhost:3000/myPageThatShowsGraphQLerrors`;

@minhna

With this code:

let theWsLinkUrl = `wss://localhost:3000/graphql`
let appApiUrl = `http://localhost:3000/graphql`;
let errorLink = `http://localhost:3000/myPageThatShowsGraphQLerrors`;


const authLinkContext = setContext((_, { headers }) => {
    // return the headers to the context so httpLink can read them
    return {
        headers: {
            ...headers,
            authorization: token || "",
        },
    };
});

const authLink = ApolloLink.from([
    errorLink,
    authLinkContext.concat(httpAuthLink),
]);

…I’m getting an authLink that looks like this:

These APIs are not easy to research. Can you provide guidance by any chance?

You don’t need a separate authLink. I need it because I designed my app like that. You don’t even need wsLink if you don’t use subscription feature.

I found a way to get the token to the server, and then use that to look up the authenticated Meteor.userId()! I’ll get the client and server code cleaned up and posted here. I still have to get subscriptions working. :slight_smile:

@minhna May I ask what pubsub you are using with Apollo Subscriptions? And could you please post the setup code? I’d previously used graphql-postgres-subscriptions, but that seems to require an older version of node.

I use graphql-redis-subscriptions

import { RedisPubSub } from "graphql-redis-subscriptions";

export const pubsub = new RedisPubSub({
});
  pubsub.publish(channelId, {
    someKey: data,
  });

The client side, I use Apollo client @apollo/client package.

If you have Meteor on the client then just use Meteor pub/sub. It’s much easier. I use Apollo because my client side is React Native.

Thanks for this great info. So that’s all the setup – I don’t have to create redis db or something like that?

I’m running this code, from the graphql-redis-subscriptions npm page:

import { RedisPubSub } from "graphql-redis-subscriptions";
import * as Redis from 'ioredis';

const options = {
    host: 'localhost',
    port: 6379,
    retryStrategy: times => {
        // reconnect after
        return Math.min(times * 50, 2000);
    }
};

const pubsub = new RedisPubSub({
    publisher: new Redis(options),
    subscriber: new Redis(options)
});

…and it’s saying “Redis is not a constructor”. Does that make any sense?