Getting Auth and Subscription to work together

Any tips on getting the two to work together? I have a function to setup the Apollo Client on the backend. getToken is a function that returns a token from Auth0 signed by the shared key

async function identify() {
  const result = await getToken();
  const httpLink = new HttpLink({
    uri: `https://${ENDPOINT}`,
    headers: { "X-Auth0-Token": result.access_token },
    fetch: fetch,
  });
  const wsLink = new WebSocketLink({
    uri: `wss://${ENDPOINT}`,
    options: {
      reconnect: true,
    },
    webSocketImpl: WebSocket,
  });
  const link = split(
    ({ query }) => {
      const definition = getMainDefinition(query);
      return (
        definition.kind === "OperationDefinition" &&
        definition.operation === "subscription"
      );
    },
    wsLink,
    httpLink
  );
  client = new ApolloClient({
    link: link,
    cache: new InMemoryCache(),
  });
  return client;
}

And a schema

type Nex @withSubscription @auth(
    query: { rule:  "{ $isAuth: { eq: \"true\" } }"}
) {
    title: String! @id
}
# Dgraph.Authorization {"VerificationKey":"<my_shared_certificate>","Header":"X-Auth0-Token","Namespace":"https://chromatic.systems/jwt/claim","Algo":"RS256","audience":["aud1"]}

My test suite covers add, delete, query with the HttpLink just fine but with Auth on my subscription test only gets blank responses

      {
        data: { queryNex: [] },
        extensions: {
          tracing: {
            version: 1,
            startTime: '2020-08-26T14:56:15.450110585Z',
            endTime: '2020-08-26T14:56:15.450934458Z',
            duration: 823979,
            execution: [Object]
          }
        }
      }

Do I need to auth the WebSocketLink in some way different than the HttpLink? Everything works with the auth removed from the schema.
Including the full test code maybe it will be helpful to others.

graph.js

const { InMemoryCache, HttpLink, split, gql } = require("@apollo/client");
const { ApolloClient } = require("@apollo/client/core");
const { setContext } = require("@apollo/client/link/context");
const { WebSocketLink } = require("@apollo/client/link/ws");
const { getMainDefinition } = require("@apollo/client/utilities");
const fetch = require("node-fetch");
const { getToken, verifyToken } = require("./token");
const WebSocket = require("ws");

let client = null;
const ENDPOINT = "<my_slash_graphql_endpoint>";

async function identify() {
  const result = await getToken();
  const httpLink = new HttpLink({
    uri: `https://${ENDPOINT}`,
    headers: { "X-Auth0-Token": result.access_token },
    fetch: fetch,
  });
  const wsLink = new WebSocketLink({
    uri: `wss://${ENDPOINT}`,
    options: {
      reconnect: true,
    },
    headers: { "X-Auth0-Token": result.access_token },
    webSocketImpl: WebSocket,
  });
  const link = split(
    ({ query }) => {
      const definition = getMainDefinition(query);
      return (
        definition.kind === "OperationDefinition" &&
        definition.operation === "subscription"
      );
    },
    wsLink,
    httpLink
  );
  client = new ApolloClient({
    link: link,
    cache: new InMemoryCache(),
  });
  return client;
}

async function queryNex(queryTitle, queryOrder) {
  const query = gql`
    query queryTheNex($titleArg: NexFilter, $orderArg: NexOrder) {
      queryNex(filter: $titleArg, order: $orderArg) {
        title
      }
    }
  `;
  let orderArg = {};
  orderArg[queryOrder] = "title";
  const titleArg = { title: { eq: queryTitle } };
  const variables = { titleArg, orderArg };

  return client.query({
    query,
    variables,
  });
}

async function getNex(title) {
  const query = gql`
    query getTheNex($datas: String!) {
      getNex(title: $datas) {
        title
      }
    }
  `;
  const variables = { datas: title };
  return client.query({
    query,
    variables,
  });
}

async function addNex(nexList) {
  const mutation = gql`
    mutation addTheNex($datas: [AddNexInput!]!) {
      addNex(input: $datas) {
        numUids
      }
    }
  `;
  const variables = { datas: nexList };
  return client.mutate({
    mutation,
    variables,
  });
}

async function deleteNex() {
  const mutation = gql`
    mutation deleteNex {
      deleteNex(filter: { title: { eq: "bar" } }) {
        msg
        numUids
      }
    }
  `;

  return client.mutate({
    mutation,
  });
}

async function subscribeNex() {
  const SUBSCRIPTION_QUERY = gql`
    subscription {
      queryNex {
        title
      }
    }
  `;
  return client.subscribe({
    query: SUBSCRIPTION_QUERY,
  });
}

module.exports = {
  queryNex,
  deleteNex,
  getNex,
  addNex,
  identify,
  subscribeNex,
};

graph.test.js

const {
  queryNex,
  deleteNex,
  getNex,
  addNex,
  identify,
  subscribeNex,
} = require("./graph");
beforeAll(async () => {
  await identify();
  return;
});
beforeAll(async () => {
  let observable = await subscribeNex();
  observable.subscribe({
    next(x) {
      //console.log(x.data.queryNex[0]);
    },
    error(err) {
      //console.log(`Finished with error: ${err}`);
    },
    complete() {
      //console.log("Finished");
    },
  });
});

test("delete old Nex", async () => {
  const result = await deleteNex();
  if (result.data.deleteNex.numUids === 0) {
    expect(result.data.deleteNex.msg).toStrictEqual("No nodes were deleted");
  } else {
    expect(result.data.deleteNex.msg).toStrictEqual("Deleted");
  }
});
test("add a Nex", async () => {
  const datas = [{ title: "bar" }];
  const result = await addNex(datas);
  expect(result.data.addNex.numUids).toStrictEqual(1);
});
test("get Nex by id", async () => {
  const id = "bar";
  const result = await getNex(id);
  expect(result.data.getNex).toStrictEqual({
    title: "bar",
    __typename: "Nex",
  });
});
test("query for the Nex", async () => {
  const id = "bar";
  const order = "desc";
  const result = await queryNex(id, order);
  expect(result.data.queryNex).toStrictEqual([
    {
      title: "bar",
      __typename: "Nex",
    },
  ]);
});

Hi, Thanks for detailed question!
Yes, adding auth over WebSocketLink is different than HttpLink. You need to pass header in connectionParams as below:

const wsLink = new WebSocketLink({
  uri: `wss://${ENDPOINT}`,
  options: {
    reconnect: true,
    connectionParams: {  "X-Auth0-Token": result.access_token, },});

Please check apollo docs https://www.apollographql.com/docs/react/data/subscriptions/
And also make sure either you are running master or in slash , because this feature is not released yet.
Let me know if you still get this or any other issue related to auth and subscriptions.

1 Like

That was it thank you.

Mod note: I split this topic from this topic