Open mmahalwy opened 6 years ago
@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!
@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.
Hm. Just kidding now. I'm looking and I don't seem to have access to the headers on axios at all. 👎
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.
@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.
Whoa, had no idea about interceptors! I like it!
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):
The interceptor is setup to work with a custom instance, therefore will automatically included if you use the instance everywhere in your app.
I use storage from redux-persist/lib/storage/index
- since that is already included as a module in this package instead of 'store'
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 https://github.com/kylecorbelli/redux-token-auth/pull/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);
});
});
};
Seems that either: