Search code examples
djangocookiesvuejs2apollovue-apollo

How to add cookies to vue-apollo request?


I use vue-cli-plugin-apollo and I want to send language chosen by user from frontend to backend via cookie.

As a vue-apollo.js I use the next template

import Vue from 'vue'
import VueApollo from 'vue-apollo'
import { createApolloClient, restartWebsockets } from 'vue-cli-plugin-apollo/graphql-client'

// Install the vue plugin
Vue.use(VueApollo)

// Name of the localStorage item
const AUTH_TOKEN = 'apollo-token'

// Http endpoint
const httpEndpoint = process.env.VUE_APP_GRAPHQL_HTTP || 'http://localhost:4000/graphql'

// Files URL root
export const filesRoot = process.env.VUE_APP_FILES_ROOT || httpEndpoint.substr(0, httpEndpoint.indexOf('/graphql'))

Vue.prototype.$filesRoot = filesRoot

// Config
const defaultOptions = {
  // You can use `https` for secure connection (recommended in production)
  httpEndpoint,
  // You can use `wss` for secure connection (recommended in production)
  // Use `null` to disable subscriptions
  wsEndpoint: process.env.VUE_APP_GRAPHQL_WS || 'ws://localhost:4000/graphql',
  // LocalStorage token
  tokenName: AUTH_TOKEN,
  // Enable Automatic Query persisting with Apollo Engine
  persisting: false,
  // Use websockets for everything (no HTTP)
  // You need to pass a `wsEndpoint` for this to work
  websocketsOnly: false,
  // Is being rendered on the server?
  ssr: false,

  // Override default apollo link
  // note: don't override httpLink here, specify httpLink options in the
  // httpLinkOptions property of defaultOptions.
  // link: myLink

  // Override default cache
  // cache: myCache

  // Override the way the Authorization header is set
  // getAuth: (tokenName) => ...

  // Additional ApolloClient options
  // apollo: { ... }

  // Client local data (see apollo-link-state)
  // clientState: { resolvers: { ... }, defaults: { ... } }
}

// Call this in the Vue app file
export function createProvider (options = {}) {
  // Create apollo client
  const { apolloClient, wsClient } = createApolloClient({
    ...defaultOptions,
    ...options,
  })
  apolloClient.wsClient = wsClient

  // Create vue apollo provider
  const apolloProvider = new VueApollo({
    defaultClient: apolloClient,
    defaultOptions: {
      $query: {
        // fetchPolicy: 'cache-and-network',
      },
    },
    errorHandler (error) {
      // eslint-disable-next-line no-console
      console.log('%cError', 'background: red; color: white; padding: 2px 4px; border-radius: 3px; font-weight: bold;', error.message)
    },
  })

  return apolloProvider
}

taken from here. All options are shown here.

I have seen in different github discussions that cookies must be placed inside headers, for example here. Then I found, that apollo-link-http has headers option, so at the end I tried different variations of ...:

httpLinkOptions: {
  headers: {

    // Tried something like:
    cookie[s]: 'language=en; path=/;'

    // and something like:
    cookie[s]: {
      language: 'en'
    }
  }
}

but no luck.

In case of cookieS I receive Error: Network error: Failed to fetch.

In case of cookie, request is sent without issues, but backend does not see language cookie.

I double-checked backend using Postman and in this case backend receives request with manually added language cookie.

Could anyone help me?


Solution

  • Found solution.

    FRONT END SETTINGS

    1. Create cookie:
    export function languageCookieSet (lang) {
      document.cookie = `language=${lang}; path=/;`
    }
    
    1. Add httpLinkOptions to defaultOptions of vue-apollo.js.
    const defaultOptions = {
      ...
    
      httpLinkOptions: {
        credentials: 'include'
      },
    
      ...
    

    BACKEND SETTINGS

    As a backend I use Django (currently v2.2.7).

    1. For development we need to use django-cors-headers
    2. My development.py now looks like:
    from .production import *
    
    CORS_ORIGIN_WHITELIST = (
        'http://localhost:8080',
    )
    CORS_ALLOW_CREDENTIALS = True
    
    INSTALLED_APPS += ['corsheaders']
    
    MIDDLEWARE.insert(0, 'corsheaders.middleware.CorsMiddleware')
    
    1. Add to production.py:
    LANGUAGE_COOKIE_NAME = 'language'
    

    The default value of LANGUAGE_COOKIE_NAME is django_language, so if it is suitable for you, change

    document.cookie = `language=${lang}; path=/;`
    

    to

    document.cookie = `django_language=${lang}; path=/;`
    
    1. Now in backend we can get frontend language:
    import graphene
    
    from django.contrib.auth import get_user_model
    from django.utils.translation import gettext as _
    
    from .views import user_activation__create_email_confirmation
    
    User = get_user_model()
    
    class UserRegister(graphene.Mutation):
        """
        mutation {
          userRegister(email: "test@domain.com", password: "TestPass") {
            msg
          }
        }
        """
    
        msg = graphene.String()
    
        class Arguments:
            email = graphene.String(required=True)
            password = graphene.String(required=True)
    
        def mutate(self, info, email, password):
            request = info.context
    
            # Here we get either language from our cookie or from
            # header's "Accept-Language" added by Browser (taken
            # from its settings)
            lang = request.LANGUAGE_CODE
            print('lang:', lang)
    
            if User.objects.filter(email=email).exists():
                # In our case Django translates this string based
                # on the cookie's value (the same as "request.LANGUAGE_CODE")
                # Details: https://docs.djangoproject.com/en/2.2/topics/i18n/translation/
                msg = _('Email is already taken')
            else:
                msg = _('Activation link has been sent to your email.')
    
                user = User(email=email)
                user.set_password(password)
                user.save()
                user_activation__create_email_confirmation(info.context, user)
    
            return UserRegister(msg=msg)
    

    Note: I have not tested yet these changes in production, but in production I use just one server where frontend and backend live behind nGinx and this is the reason why CORS settings live in development.py instead of production.py. Also in production credentials: 'include' possibly could be changed to credentials: 'same-origin' (ie more strict).