Vue-apollo: How to set up Apollo Client 2.0, ApolloLink with subscriptions etc..

Created on 7 Nov 2017  路  18Comments  路  Source: vuejs/vue-apollo

I spent some time to figure it out, so just in case anyone wants to try:

// Apollo imports
import ApolloClient from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { ApolloLink, concat, split } from 'apollo-link';
import { InMemoryCache } from 'apollo-cache-inmemory'
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';

//Vue imports
import Vue from 'vue'
import VueApollo from 'vue-apollo'
import router from './router'

//Component imports
import App from './App'
import VModal from 'vue-js-modal'

import { GC_USER_ID, GC_AUTH_TOKEN } from './constants/settings'

import store from './store/index' // Vuex

const httpLink = new HttpLink({ uri: 'https://api.graph.cool/simple/v1/xxxxxxxxx' });

const authMiddleware = new ApolloLink((operation, forward) => {
  // add the authorization to the headers
  operation.setContext({
    headers: {
      authorization: localStorage.getItem(GC_AUTH_TOKEN) || null,
    }
  });
  return forward(operation);
})

// Set up subscription
const wsLink = new WebSocketLink({
  uri: `wss://subscriptions.us-west-2.graph.cool/v1/xxxxxxxx`,
  options: {
    reconnect: true
  }
});

// using the ability to split links, you can send data to each link
// depending on what kind of operation is being sent
const link = split(
  // split based on operation type
  ({ query }) => {
    const { kind, operation } = getMainDefinition(query);
    return kind === 'OperationDefinition' && operation === 'subscription';
  },
  wsLink,
  httpLink,
);

const apolloClient = new ApolloClient({
  link: concat(authMiddleware, link),
  cache: new InMemoryCache()
});

Vue.use(VueApollo)
Vue.use(VModal, { dialog: true })

Vue.config.productionTip = false

const apolloProvider = new VueApollo({
  defaultClient: apolloClient,
  defaultOptions: {
    $loadingKey: 'loading'
  },
  errorHandler (error) {
    console.log('Global error handler')
    console.error(error)
  }
})

const userId = localStorage.getItem(GC_USER_ID)
/* eslint-disable no-new */
window.vm = new Vue({
  el: '#app',
  store,
  apolloProvider,
  router,
  data: {
    userId
  },
  render: h => h(App)
})

Most helpful comment

Works great, however I needed to do some adjustments to authorization, in case anyone needs it, this worked for me.

instead of:

const authMiddleware = new ApolloLink((operation, forward) => {
  // add the authorization to the headers
  operation.setContext({
    headers: {
      authorization: localStorage.getItem(GC_AUTH_TOKEN) || null,
    }
  });
  return forward(operation);
})

used:

const token = localStorage.getItem(GC_AUTH_TOKEN) || null
const authMiddleware = new ApolloLink((operation, forward) => {
  // add the authorization to the headers
  operation.setContext({
    headers: {
      authorization: `Bearer ${token}`
    }
  })
  return forward(operation)
})

Thanks @kjetilge !!!

All 18 comments

Works great, however I needed to do some adjustments to authorization, in case anyone needs it, this worked for me.

instead of:

const authMiddleware = new ApolloLink((operation, forward) => {
  // add the authorization to the headers
  operation.setContext({
    headers: {
      authorization: localStorage.getItem(GC_AUTH_TOKEN) || null,
    }
  });
  return forward(operation);
})

used:

const token = localStorage.getItem(GC_AUTH_TOKEN) || null
const authMiddleware = new ApolloLink((operation, forward) => {
  // add the authorization to the headers
  operation.setContext({
    headers: {
      authorization: `Bearer ${token}`
    }
  })
  return forward(operation)
})

Thanks @kjetilge !!!

Why use localstorage instead of cookies? It won't be shared across subdomains and http/https

Why not use websockets for everything? Why split?

I found an issue. After set the the token, we have to refresh the web page to get authorization to work.

Solved: https://github.com/Akryum/vue-apollo/issues/183

Just my 2c for future visitors (and those who asked the questions):

  • I also prefer HttpOnly cookies. In the browser (not in Node), you can use credentials: 'include' to pass cookies to your API without having them accessible via JS. This is the safest approach I've found so far for protecting auth tokens. Granted, you'll need to set up your API to look for the auth token in both a cookie and/or auth header. I have my back-end look for the cookie auth token first, and then write that to the request's Authorization/Bearer header. Then, business as usual.

  • I've been using WS on the server, and HTTP in the browser. In my particular case, the back-end isn't accepting cookies (by choice, and due to web socket's lack of CORS; which results in a CSWSH vulnerability), so the only way to send the auth token in the upgrade handshake would be to make it accessible to JS in the browser. You can do this by setting the store in nuxtServerInit and accessing it via window.__NUXT__.state.authToken. The problem is that now your auth token is accessible to all of JS, and you may-or-may-not feel comfortable doing that.

Hi @bjunc

Can you make an example repo about this?

Yeah, I think I can do that. I was actually planning on doing a Medium article about this setup (along with the back-end; which is in Elixir).

Also worth noting, is that you can use @nuxtjs/proxy to make your browser-based queries/mutations. You set your GraphQL URI to /graphql, and then proxy this to the actual API endpoint. In doing this, the HttpOnly cookie is shared between the API and the Nuxt app. That allows you to use the cookie for page auth; as well as for API requests. It also negates the CORS pre-flight and other origin related issues. So at no point is your auth token accessible via JS in the browser.

I'm currently building a full demo app in the tests/demo folder that will also be used for the e2e tests, and it already has a user system with authentication.

The solution above to set authentication headers via middleware uses apollo-link and apollo-client. Is it possible to have middlewares using apollo-boost to set custom headers dynamically for each request?

Are there any examples of using the default out-of-the-box boilerplate with bearer tokens?

As in, when using vue add apollo in vue-cli where do I set the token I get from my auth provider? (auth0)

@hades200082 in your vue-apollo.js scaffolded out by the cli tool, there's a getAuth() method in the options, you can ues that to return the token.

i.e.

  // Override the way the Authorization header is set
  getAuth: () => {
    // get the authentication token from local storage if it exists
    const token = localStorage.getItem(AUTH_TOKEN)
    // return the headers to the context so httpLink can read them
    if (token) {
      return 'Bearer ' + token
    } else {
      return ''
    }
  }

Works great, however I needed to do some adjustments to authorization, in case anyone needs it, this worked for me.

instead of:

const authMiddleware = new ApolloLink((operation, forward) => {
  // add the authorization to the headers
  operation.setContext({
    headers: {
      authorization: localStorage.getItem(GC_AUTH_TOKEN) || null,
    }
  });
  return forward(operation);
})

used:

const token = localStorage.getItem(GC_AUTH_TOKEN) || null
const authMiddleware = new ApolloLink((operation, forward) => {
  // add the authorization to the headers
  operation.setContext({
    headers: {
      authorization: `Bearer ${token}`
    }
  })
  return forward(operation)
})

Thanks @kjetilge !!!

How to do this for the connection_terminated? I want to send a payload together

FF to 2019: this is the new approach

import { ApolloClient } from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { InMemoryCache } from 'apollo-cache-inmemory';

const httpLink = createHttpLink({
  uri: '/graphql',
});

const authLink = setContext((_, { headers }) => {
  // get the authentication token from local storage if it exists
  const token = localStorage.getItem('token');
  // return the headers to the context so httpLink can read them
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : "",
    }
  }
});

const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache()
});

basically the change is that use of setContext, which is to my opinion cleaner.

This is my entire apollo boot file when using subscriptions:

import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { setContext } from 'apollo-link-context';
import { split } from 'apollo-link';
import { HttpLink } from 'apollo-link-http';
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';

// You should use an absolute URL here
const options = {
  httpUri: process.env.GRAPHQL_HTTP_ENDPOINT || 'http://localhost:7001/graphql',
  wsUri:   process.env.GRAPHQL_WS_ENDPOINT  || 'ws://localhost:7001/graphql',
};

let link = new HttpLink({
  uri: options.httpUri,
});

// Create the subscription websocket link if available
if (options.wsUri) {
  const wsLink = new WebSocketLink({
    uri:     options.wsUri,
    options: {
      reconnect: true,
    },
  });

  // using the ability to split links, you can send data to each link
  // depending on what kind of operation is being sent
  link = split(
      // split based on operation type
      ({ query }) => {
        const definition = getMainDefinition(query);
        return definition.kind === 'OperationDefinition' &&
            definition.operation === 'subscription';
      },
      wsLink,
      link,
  );
}


const authLink = setContext((_, { headers }) => {
  // get the authentication token from local storage if it exists
  const token = localStorage.getItem('authorization_token');
  // return the headers to the context so httpLink can read them
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : '',
    },
  };
});


export const cache = new InMemoryCache();

// Create the apollo client instance
export default new ApolloClient({
  link: authLink.concat(link),
  cache,
  connectToDevTools: true,
});

@Akryum @emahuni The middleware has no affect on webSocketLink.
the headers seems to only be set via connectionParams on initialization.
Is there a way to change the headers dynamically? in case the token has been refreshed

You have to restart the websocket connection.

@Akryum how do i do that without restarting apollo instance?

Was this page helpful?
0 / 5 - 0 ratings