Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setting the headers on global axios does not work #28

Open
mmahalwy opened this issue Feb 20, 2018 · 7 comments
Open

Setting the headers on global axios does not work #28

mmahalwy opened this issue Feb 20, 2018 · 7 comments

Comments

@mmahalwy
Copy link

Seems that either:

  1. Versions are not aligned
  2. Different modules of axios in the client
  3. Not sure :S
@fkotsian
Copy link

fkotsian commented Feb 20, 2018

@mmahalwy I had a similar issue; As far as I can tell you can set the headers, but your underlying problem is that devise_token_auth itself changes the token on each request to the API. Therefore the headers change at each request.

Solution: Other packages like redux-auth (now outdated) wrap fetch/axios in order to re-set the headers on each request. You could do that yourself or set change_headers_on_each_request to false (though it should be noted that this allows anyone who intercepts a token to use it to make requests):

# config/initializers/devise_token_auth.rb
config.change_headers_on_each_request = false

Hope helps!

@mmahalwy
Copy link
Author

mmahalwy commented Feb 20, 2018

@fkotsian yeah, that might be a solution. I think perhaps this library should be request-library agnostic (maybe I want to use fetch? or superagent? That way, it should be the onus on the application developer to decide how to store the credentials and default it in the headers.

@fkotsian
Copy link

Hm. Just kidding now. I'm looking and I don't seem to have access to the headers on axios at all. 👎

@fkotsian
Copy link

fkotsian commented Feb 20, 2018

For future reference: copied solution from here - axios/unable to set or override global headers

// services/axios.js
import axios from 'axios'

export default function withStoredHeaders() {
  return axios.create({
    headers: {
      'access-token': localStorage.getItem('access-token'),
      'token-type': localStorage.getItem('token-type'),
      'client': localStorage.getItem('client'),
      'expiry': localStorage.getItem('expiry'),
      'uid': localStorage.getItem('uid'),
    }
  })
}

// subsequent: 
import axios from 'services/axios'
axios().get(...)

Yes this creates new axios instances on each action, would love thoughts on how to clean up.

@mmahalwy
Copy link
Author

mmahalwy commented Feb 20, 2018

@fkotsian I'd recommend doing this actually:

// ./utils/auth.js
export const setupInterceptors = () => {
  axios.interceptors.request.use(
    (config) => {
      // eslint-disable-next-line
      config.headers = {
        ...config.headers,
        'access-token': store.get('auth')['access-token'],
        client: store.get('auth').client,
        uid: store.get('auth').uid,
      };

      return config;
    },
    error =>
      // Do something with request error
      Promise.reject(error),
  );

  axios.interceptors.response.use(
    (response) => {
      if (response.headers['access-token']) {
        setAuthHeaders(response.headers);
        persistAuthHeadersInDeviceStorage(response.headers);
      }

      return response;
    },
    error =>
      // Do something with response error
      Promise.reject(error),
  );
};

// wherever you'd like to use this
setupInterceptors();

Granted, I ended up rewriting some of this package for my own use in my own app, so I am using the store library and not localStorage directly.

@fkotsian
Copy link

Whoa, had no idea about interceptors! I like it!

@rg-najera
Copy link

rg-najera commented Aug 10, 2018

Just in case someone was trying both approaches, this is what I came up with.

A few differences with my approach (might be what @mmahalwy had in mind):

  1. The interceptor is setup to work with a custom instance, therefore will automatically included if you use the instance everywhere in your app.

  2. I use storage from redux-persist/lib/storage/index - since that is already included as a module in this package instead of 'store'

  3. I use a custom version of setAuthHeaders and persistAuthHeadersInDeviceStorage where the values will only be added if they are returned as a response from the api server. This is important if you are changing headers on every request. Similar approach to my PR Fixes: Access Token in Persistent Storage is removed on hard refresh. #46

utils/api.js

import axios from 'axios';
import storage from 'redux-persist/lib/storage/index';
import { persistor, store } from '../configureStore';
import {
  setAuthHeaders,
  persistAuthHeadersInDeviceStorage
} from './auth-tools';

/* Setup Keys Constant */
const authHeaderKeys = [
  'access-token',
  'token-type',
  'client',
  'expiry',
  'uid'
];

/* Utility Function to create header object from Storage
 * @returns {object}
 */
async function getHeadersFromStorage() {
  return {
    'access-token': await storage.getItem('access-token'),
    client: await storage.getItem('client'),
    uid: await storage.getItem('uid'),
    expiry: await storage.getItem('expiry')
  };
}

/*
 * The function that subscribes to our redux store changes
 * Sets up new api headers for the global axios settings.
 * https://github.com/axios/axios#global-axios-defaults
 * Caveat - this will change the headers for every axios request
 * not just the one used for the api instance.
 */

function setNewApiHeaders() {
  getHeadersFromStorage().then(headers => {
    authHeaderKeys.forEach(key => {
      axios.defaults.headers.common[key] = headers[key];
    });
  });
}

/*
 * Adds a change listener. https://redux.js.org/api/store#subscribe-listener
 * It will be called any time an action is dispatched, and some
 * part of the state tree may potentially have changed. You may then
 * call getState() to read the current state tree inside the callback.
 */

store.subscribe(() => setNewApiHeaders());

/*
 * Setups up a new instance of axios.
 * https://github.com/axios/axios#creating-an-instance
 *
 */
const api = axios.create({
  // baseURL: '/api/v1',
  headers: {
    'Content-Type': 'application/json'
  }
});

/*
 * Axios Request Interceptor.
 * Before each request, use the values in local storage to set new headers.
 */

api.interceptors.request.use(
  async config => {
    const newConfig = config;
    const newHeaders = await getHeadersFromStorage();
    newConfig.headers = {
      ...config.headers, // Spread config headers
      ...newHeaders // Spread new headers
    };
    return newConfig;
  },
  error =>
    // Reject the promise
    Promise.reject(error)
);

/*
 * Axios Respose Interceptor.
 * After each response, use the values in local storage to set new headers.
 */

api.interceptors.response.use(
  async response => {
    // Only change headers when access-token is returned in response
    if (response.headers['access-token']) {
      await setAuthHeaders(response.headers);
      await persistAuthHeadersInDeviceStorage(response.headers);
    }

    return response;
  },
  error =>
    // Reject the promise
    Promise.reject(error)
);

// Uncomment (for testing) to use the api in the console
if (typeof window !== 'undefined') {
  window.consoleApi = api;
}

export default api;

And these are the two customized tools setAuthHeaders & persistAuthHeadersInDeviceStorage that will persist the headers in storage as well as set the axios.defaults.headers.common value

authtools.js

import axios from 'axios';
import storage from 'redux-persist/lib/storage/index';

const authHeaderKeys = [
  'access-token',
  'token-type',
  'client',
  'uid',
  'expiry'
];

export const setAuthHeaders = headers => {
  authHeaderKeys.forEach(key => {
    storage.getItem(key).then(fromStorage => {
      const value = headers[key] || fromStorage;
      axios.defaults.headers.common[key] = value;
    });
  });
};

export const persistAuthHeadersInDeviceStorage = headers => {
  authHeaderKeys.forEach(key => {
    storage.getItem(key).then(fromStorage => {
      const value = headers[key] || fromStorage;
      storage.setItem(key, value);
    });
  });
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants