Vue.js-Electron application with an Apollo client does not receive cookies from a remote API server

376 Views Asked by At

I try to build an application with a GraphQL backend (based on Node.js and graphql-yoga). The server is hosted on a Linux machine with nginx as a reverse proxy. The proxy configuration is listed below.

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    include snippets/self-signed.conf;
    include snippets/ssl-params.conf;

    server_name url.example.com;

    charset utf-8;

    # Always serve index.html for any request
    location / {
        root /srv/app/public;
        try_files $uri /index.html;
    }

    location /graphql {
        allow all;

        proxy_pass http://localhost:3000$request_uri;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;

   }

    location /playground {
        # Additional configuration
    }


    location /subscriptions {
        # Additional configuration
    }

    error_log  /var/log/nginx/vue-app-error.log;
    access_log /var/log/nginx/vue-app-access.log;

}

server {
    listen 80;
    listen [::]:80;

    server_name url.example.com;

    return 302 https://$server_name$request_uri;

To avoid CORS problems, I had to add the following entry in my servers configuration and it works.

const options = {
  [
    ...
  ]
  /**
   * Cross-Origin Resource Sharing (CORS)
   */
  cors: {
    credentials: true,
    origin: ['http://localhost:8080', 'https://url.example.com'], // frontend url
  },
};

The authentication is based on JWT tokens packed into http-only cookies for access and refresh tokens. This is working as expected when I access my server with a browser and the cookies are shown in the developer tools. Within the AuthData, I additionally provide the username and a JWT token as a return value.

{
    "data":{
        "login":{
            "user":"admin",
            "name":" Admin",
            "token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJhZG1pbiIsIm5hbWUiOiIgQWRtaW5pc3RyYXRvciIsImVtYWlsIjoiYWRtaW5AZXhhbXBsZS5jb20iLCJpYXQiOjE2NDg0ODM2MDcsImV4cCI6MTY0ODQ4NzIwN30.Dol41LStkscXrlGn3GJotf83k_d2EImyvDU68Dg8Bvw"
        }
    },
    "loading":false,
    "networkStatus":7
}

Now I wanted to play around with Electron, because some use cases of the application would be much easier. The stack is Vue.js and Electron with Tailwind and the Apollo client (I use the Apollo UploadClient here) as listed below.

import {
  ApolloClient,
  ApolloLink,
  DefaultOptions,
  InMemoryCache,
} from '@apollo/client/core';
import { setContext } from '@apollo/client/link/context';
import { createUploadLink } from 'apollo-upload-client';

const backendServer = 'https://url.example.com/graphql';

const httpLink = createUploadLink({
  uri: backendServer,
  credentials: 'include',
});

// Cache implementation
const cache = new InMemoryCache({
  addTypename: false,
}); //

const defaultOptions = {
  watchQuery: {
    fetchPolicy: 'no-cache',
  },
  query: {
    fetchPolicy: 'no-cache',
    errorPolicy: 'all',
  },
  mutate: {
    errorPolicy: 'all',
  },
} as DefaultOptions;

const authLink = setContext((_, { headers }) => ({
  headers: {
    ...headers,
    /**
       * use a custom header tag to classify the application
       * for authentication handling in the backend
       */
    'app-type': 'web',
  },
}));

const apolloClient = new ApolloClient({
  link: ApolloLink.from([authLink, httpLink]),
  cache,
  defaultOptions,
  resolvers: {},
});

export default apolloClient;

This client is bound to my Vue.js instance via @vue/apollo-composable. So I think it is only available in the renderer process. React examples I found on the Internet were built the same way.

Now the problem: When I run server and client (Electron) application on my development machine, everything works as expected, and I can see the cookies in the application tab in the developer tools. I also can access the API without issues.

When I now bind my ApolloClient to the remote API server I do not receive any cookies. From the logs I can see that I receive the AuthData like above from the server, but not the cookies. So any further request results in a custom error message "unauthenticated access" provided by my API server.

{
    "loading":false,
    "networkStatus":8,
    "error":{
        "graphQLErrors":[
            {
                "message":"user is not authenticated...redirect to the login page",
                "locations":[
                    {"line":2,"column":3}
                ],
                "path":[
                    "users"
                ]
            }
        ],
        "clientErrors":[],
        "networkError":null,
        "message":"user is not authenticated...redirect to the login page"
    },"errors":[
        {
            "message":"user is not authenticated...redirect to the login page",
            "locations":[
                {"line":2,"column":3}
            ],
            "path":[
                "users"
            ]
        }
    ]
}

What am I missing or doing wrong, or what else could I test?

Additional tested direct access

I additionally tested direct accessing the gql API from the electron client without the nginx proxy but still no success. I get the login correctly but I'm not able to see the cookies provided by the server.

any suggestions?

0

There are 0 best solutions below