Apollo-client: empty headers in in afterware link

Created on 8 Nov 2017  ·  13Comments  ·  Source: apollographql/apollo-client

Intended outcome:

I'm setting a custom header on the response I'm supposed being able to access in the afterware link

Actual outcome:

The headers object is empty

How to reproduce the issue:

Some context:
I have a custom header in response visible in network tab but not in the response object.

I know that the custom header is stripped away by the browser as this is a CORS request so I put it into Access-Control-Expose-Headers.

Now the browser exposes the custom header just fine as you may see in the screenshot and I should be able to access it within the afterwareLink. Though that's not the case.

headers

The headers object is always empty though getContext yields forceFetch and cache fields

console

I'm using:
CLIENT:
"apollo": "0.2.2",
"apollo-cache-inmemory": "1.1.0",
"apollo-client": "2.0.2",
"apollo-link": "1.0.0",
"apollo-link-context": "1.0.0",
"apollo-link-error": "1.0.0",
"apollo-link-http": "1.1.0",
"graphql": "0.11.7",
"graphql-tag": "2.5.0",
"vue": "2.5.3",
"vue-apollo": "3.0.0-alpha.2",

SERVER:
"apollo-server-express": "1.2.0",
"body-parser": "^1.17.1",
"cors": "^2.8.3",
"express": "^4.15.2",
"graphql-tools": "^1.2.3",

Afterware code:

const afterwareLink = new ApolloLink((operation, forward) => {
  const { headers } = operation.getContext();
  console.log(operation.getContext());
  if (headers) {
    const refreshToken = headers.get('x-refresh-token');
    console.log('refreshToken', refreshToken);
  }
  return forward(operation);
}

Version

Most helpful comment

@Knaackee
That's ok I guess as it is the same thing I see in mine.

However headers.get('x-refresh-token') correctly retrieves the token and I can both console.log it and set it in the localStorage in the afterware code.

Do you mean it is not working for you? I mean retrieving the header using headers.get('x-refresh-token')

A complete example:

import { ApolloLink } from 'apollo-link';
import { createHttpLink } from 'apollo-link-http';

const httpLink = createHttpLink({ uri: 'http://localhost:3001/graphql' });

const afterwareLink = new ApolloLink((operation, forward) => {
  return forward(operation).map(response => {
    const context = operation.getContext();
    const { response: { headers } } = context;

    if (headers) {
      const token = headers.get("x-refresh-token");

      if (token) {
        localStorage.setItem("token", token);
      }

    }
    return response;
  });
});

// use with apollo-client
const link = afterwareLink.concat(httpLink);

All 13 comments

I ran into this issue as well and found that I had to get the context after forward(operation).map in order for the correct response object with headers to be available.

Afterware code:

const afterwareLink = new ApolloLink((operation, forward) => {
  return forward(operation).map(response => {
    const context = operation.getContext();
    const { response: { headers } } = context;

    if (headers) {
      const refreshToken = headers.get('x-refresh-token');

      if (refreshToken) {
        localStorage.setItem('refreshToken', refreshToken);
      }
    }

    return response;
  });
});

@Thr1ve You saved my day! Although I was looking in that direction I wasn't able to get it right.

Thanks a lot :+1:

Now I'm just wondering if that's the way it was supposed to be. In the Context section of the readme it's written:

This link also attaches the response from the fetch operation on the context as response so you can access it...

Now that you've post it I have to say that it reassembles your code example although it wasn't so obvious to me.

@ecerroni we should def improve that portion of the docs! It is on the ApolloLink repo, would you be up for improving that?

@Thr1ve thanks for the assist here!

After trying to get it to work for several hours, i have to ask here.
I have the exact same problem and tried what @Thr1ve suggested.

I use the same dependencies and versions as @ecerroni

I tried using "Access-Control-Expose-Headers: *" and "Access-Control-Expose-Headers: x-refresh-token".

heres is what i see in the chrome network tab

screen shot 2017-11-16 at 11 26 41 1

and the console.log of the response.

screen shot 2017-11-16 at 11 29 14

What i am missing here ?

Thanks a lot @jbaxleyiii and the whole apollo team!

@Knaackee
That's ok I guess as it is the same thing I see in mine.

However headers.get('x-refresh-token') correctly retrieves the token and I can both console.log it and set it in the localStorage in the afterware code.

Do you mean it is not working for you? I mean retrieving the header using headers.get('x-refresh-token')

A complete example:

import { ApolloLink } from 'apollo-link';
import { createHttpLink } from 'apollo-link-http';

const httpLink = createHttpLink({ uri: 'http://localhost:3001/graphql' });

const afterwareLink = new ApolloLink((operation, forward) => {
  return forward(operation).map(response => {
    const context = operation.getContext();
    const { response: { headers } } = context;

    if (headers) {
      const token = headers.get("x-refresh-token");

      if (token) {
        localStorage.setItem("token", token);
      }

    }
    return response;
  });
});

// use with apollo-client
const link = afterwareLink.concat(httpLink);

@ecerroni

Sorry i missed that. Now its working as expected. Thank you!

In addition i had to change Access-Control-Expose-Headers: * to Access-Control-Expose-Headers: x-refresh-token to make it work.

Thanks a lot!

Hi, sorry @ecerroni , I got same issue with Express but not solve it out yet, would you please to take a look?

  1. I've set the response headers in resolver like this
    screen shot 2017-11-30 at 6 32 03 pm

    1. Response headers at client did not get it

      screen shot 2017-11-30 at 6 33 12 pm

  2. I tried to retrieve it (but sure I couldn't get it):
const authLink = new ApolloLink((operation, forward) => {
  const token = localStorage.getItem(AUTH_TOKEN)
  const refresh_token = localStorage.getItem(REFRESH_TOKEN)
  if (token) {
    operation.setContext({
      headers: {
        'x-token': token,
        'x-refresh-token': refresh_token
      }
    })
  }
  return forward(operation).map(response => {
    const context = operation.getContext()
    const { response: { headers } } = context
    if (headers) {
      const token = headers.get("x-token")
      const refresh_token = headers.get("x-refresh-token")
      if (token) {
        localStorage.setItem(AUTH_TOKEN, token)
        localStorage.setItem(REFRESH_TOKEN, refresh_token)
      }
    }
    return response
  })
})

nvm, I got this, pass the token to headers on the auth user context, not on the resolver

res.set('Access-Control-Expose-Headers', '*') working for me

Just in case someone wants to know more about Access-Control-Expose-Headers: it's useful when making cors request because by default server only exposes CORS-safelisted request-header. Adding this header will allow you to expose any http headers (including custom headers) to client.

Wasted a couple of hours too :)

Looks like if you console.log headers, it will appear as empty. Iterating over it doesn't work either.

Whereas getting the header by name: context.response.headers.get('...') magically works. Now only to find the Chrome engineer who is responsible for this behavior...

I read carefully all of your posts, but I still cannot have access to the headers.
Here you have my code :

const middlewareAuthLink = new ApolloLink((operation, forward) => {
  const token = localStorage.getItem(AUTH_TOKEN)
  const authorizationHeader = token ? `Bearer ${token}` : null
  operation.setContext({
    headers: {
      authorization: authorizationHeader
    }
  })
  return forward(operation).map(response => {
    const context = operation.getContext()
    const {
      response: { headers }
    } = context
    if (headers) {
      const refresh_token = headers.get('x-token')
      console.log(refresh_token) // index.js:37
    }
    return response
  })
})

The response:
image

and the console:
image

What am I missing?

related topic: https://stackoverflow.com/questions/47443858/apollo-link-response-headers

I got it

@ecerroni

Sorry i missed that. Now its working as expected. Thank you!

In addition i had to change Access-Control-Expose-Headers: * to Access-Control-Expose-Headers: x-refresh-token to make it work.

Thanks a lot!

Must be set in the backend! (express). Then the frontend can access to this field!
Now it works!

My full code is here: https://stackoverflow.com/a/58986484/5449450

Was this page helpful?
0 / 5 - 0 ratings