'Vue Apollo: No Authorization token in websocket

I have a fastify backend that runs graphql server. Without authentication hook at the server, my all graphql request and subscription in my vue app works fine.

Adding jwt authentication only graphql query and mutation works but my realtime subsciption keeps giving me unauthorized 401 error.

{"type":"UnauthorizedError","message":"No Authorization was found in request.headers","stack":"UnauthorizedError: No Authorization was found in request.headers\n at lookupToken

Theres an answer online saying to override wsclient with this code, but it does not resolve my issue.

  wsClient.connectionParams = () => {
    return {
      headers: {
        Authorization: localStorage.getItem(AUTH_TOKEN) ? `Bearer ${localStorage.getItem(AUTH_TOKEN)}` : ''
      }
    }
  }

My Setup vue-apollo.js

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

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

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

// 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:3000/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 => {
    //  const token = 'Bearer ' + localStorage.getItem(AUTH_TOKEN);
    //  return token || '';
    // },

    // 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,
    });

    wsClient.connectionParams = () => {
        return {
            headers: {
                authorization: localStorage.getItem('apollo-token'),
            },
        };
    };
    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;
}

// Manually call this when user log in
export async function onLogin(apolloClient, token) {
    if (typeof localStorage !== 'undefined' && token) {
        localStorage.setItem(AUTH_TOKEN, token);
    }
    if (apolloClient.wsClient) restartWebsockets(apolloClient.wsClient);
    try {
        await apolloClient.resetStore();
    } catch (e) {
        // eslint-disable-next-line no-console
        console.log('%cError on cache reset (login)', 'color: orange;', e.message);
    }
}

// Manually call this when user log out
export async function onLogout(apolloClient) {
    if (typeof localStorage !== 'undefined') {
        localStorage.removeItem(AUTH_TOKEN);
    }
    if (apolloClient.wsClient) restartWebsockets(apolloClient.wsClient);
    try {
        await apolloClient.resetStore();
    } catch (e) {
        // eslint-disable-next-line no-console
        console.log('%cError on cache reset (logout)', 'color: orange;', e.message);
    }
    {
        console.log('%cError on cache reset (logout)', 'color: orange;', e.message);
    }
}

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

SERVER RESPONSES

When I console log the queries or mutations

{
  host: 'localhost:3000',
  connection: 'keep-alive',
  'content-length': '205',
  'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="100", "Google Chrome";v="100"',
  accept: '*/*',
  'content-type': 'application/json',
  authorization: 'Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlJiQ2RxSzIwMm1URmx5NzY4X1VMWSJ9.eyJpc3MiOiJodHRwczovL2Rldi01M3c3NXJ3Yi5hdS5hdXRoMC5jb20vIiwic3ViIjoiYXV0aDB8NjI0NjQ0YWIyM2M0NjMwMDcwNTE0ZTY0IiwiYXVkIjpbImh0dHBzOi8vYXBpLmd1YXJkZXguY28ubnovIiwiaHR0cHM6Ly9kZXYtNTN3NzVyd2IuYXUuYXV0aDAuY29tL3VzIHByb2ZpbGUgZW1haWwifQ.e8OJDXmICjo582blGuCOCKtYOsrRcQrG8qp_ABcFqg6faMkw0xJNbJ3r0vvktBSjN9Kp93cIHlEr_ljSpZyb9roJbCShE7LXageX3wgvtq_nawQLOMTn-aC6AQqELjvgIEsuPb998hXioF0bXljdLnUy3Fi71cZby2uciqJes7iU2j5K2VuqGNLytNJc_RL9Cin7Z_7EE5J4_Djql1jtfN7boCrAMxjRIhs2EgQ_Y0DSr53WkdssBDcKfK1fk_cxksRXRFxhbKfrJSAWIdrGf6_hlVL8WdOYRNSlzi79_x0wNPGA0zzuEX458x0ffqXvBgnb7DUC_AsT2028e1tY',
  'sec-ch-ua-mobile': '?0',
  'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36',
  'sec-ch-ua-platform': '"Windows"',
  origin: 'http://localhost:8080',
  'sec-fetch-site': 'same-site',
  'sec-fetch-mode': 'cors',
  'sec-fetch-dest': 'empty',
  referer: 'http://localhost:8080/',
  'accept-encoding': 'gzip, deflate, br',
  'accept-language': 'en-US,en;q=0.9'
}

When I console log the subscription headers I get:

{
  host: 'localhost:3000',
  connection: 'Upgrade',
  pragma: 'no-cache',
  'cache-control': 'no-cache',
  'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36',
  upgrade: 'websocket',
  origin: 'http://localhost:8080',
  'sec-websocket-version': '13',
  'accept-encoding': 'gzip, deflate, br',
  'accept-language': 'en-US,en;q=0.9',
  cookie: '_legacy_auth.SECRECT.is.authenticated=true; auth.is.authenticated=true',
  'sec-websocket-key': 'iFNve3jfWvxmJRCsrEH+Og==',
  'sec-websocket-extensions': 'permessage-deflate; client_max_window_bits',
  'sec-websocket-protocol': 'graphql-ws'
}


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source