Both in fetchBaseQuery and inside the single query definition when injecting endpoints RTK Query sets all my headers to lowercase, is there a way to keep the capitol letters inside the headers?
I know it's best to keep headers case insensitive, but I can't access the backend configurations since it's an external service.
code examples
const extendedApi = BaseApi.injectEndpoints({
endpoints: (build) => ({
getType: build.query<string[], string>({
query: (param) => ({
url: /test/${param}/call,
headers: {
Authorization: "Bearer token",
}
}),
}),
}),
overrideExisting: false
});
export const BaseApi = createApi({
baseQuery: fetchBaseQuery({
baseUrl: process.env.REACT_APP_API_URL,
prepareHeaders: (headers) => {
headers.set('Authorization', "Bearer token")
return headers
}
}),
endpoints: () => ({}),
});
Related
I am new to using Axios configuration. **So I am wondering if it is possible to set axios header dynamically?**
Because the end points I am calling right now need a Authentication and different authentication for different api, so I want make a change to the created axios instance’s header when token is expired and with different URL.
Here is my current code:
in config.js
import axios from 'axios'
// to get Authorization for api_1
const {access_token_1} = axios.get('url/access_token_1')
// to get Authorization for api_2
const {access_token_2} = axios.get('url/access_token_2')
export const instance = axios.create({
headers: { Authorization: `Bearer ${access_token_1}` },
})
My Api_1 and 2 call
//Api_1
export const getCountry = async (country: string) => {
const response = await instance.get(
`/sas/${country}`
)
return response.data
}
//Api_2
export const getCity = async (city: string) => {
const response = await instance.get(
`/sps/${city}`
)
return response.data
}
I know header can be set again by certain method, but how could I set it again only when it’s expired and set the instance with right authentication for certain Api
Have a look at this documentation, you can create/update headers and pass them to your axios instance. I think this examples might help a little
axios.defaults.baseURL = 'https://api.example.com';
// Important: If axios is used with multiple domains, the AUTH_TOKEN will be sent to all of them.
// See below for an example using Custom instance defaults instead.
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
const options = {
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
data: qs.stringify(data),
url,
};
axios(options);
axios.get('https://example.com/getSomething', {
headers: {
Authorization: 'Bearer ' + token //the token is a variable which holds the token
}
})
I have a react/redux application that fetches a token from an api server. After the user authenticates I'd like to make all axios requests have that token as an Authorization header without having to manually attach it to every request in the action. I'm fairly new to react/redux and am not sure on the best approach and am not finding any quality hits on google.
Here is my redux setup:
// actions.js
import axios from 'axios';
export function loginUser(props) {
const url = `https://api.mydomain.com/login/`;
const { email, password } = props;
const request = axios.post(url, { email, password });
return {
type: LOGIN_USER,
payload: request
};
}
export function fetchPages() {
/* here is where I'd like the header to be attached automatically if the user
has logged in */
const request = axios.get(PAGES_URL);
return {
type: FETCH_PAGES,
payload: request
};
}
// reducers.js
const initialState = {
isAuthenticated: false,
token: null
};
export default (state = initialState, action) => {
switch(action.type) {
case LOGIN_USER:
// here is where I believe I should be attaching the header to all axios requests.
return {
token: action.payload.data.key,
isAuthenticated: true
};
case LOGOUT_USER:
// i would remove the header from all axios requests here.
return initialState;
default:
return state;
}
}
My token is stored in redux store under state.session.token.
I'm a bit lost on how to proceed. I've tried making an axios instance in a file in my root directory and update/import that instead of from node_modules but it's not attaching the header when the state changes. Any feedback/ideas are much appreciated, thanks.
There are multiple ways to achieve this. Here, I have explained the two most common approaches.
1. You can use axios interceptors to intercept any requests and add authorization headers.
// Add a request interceptor
axios.interceptors.request.use(function (config) {
const token = store.getState().session.token;
config.headers.Authorization = token;
return config;
});
2. From the documentation of axios you can see there is a mechanism available which allows you to set default header which will be sent with every request you make.
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
So in your case:
axios.defaults.headers.common['Authorization'] = store.getState().session.token;
If you want, you can create a self-executable function which will set authorization header itself when the token is present in the store.
(function() {
String token = store.getState().session.token;
if (token) {
axios.defaults.headers.common['Authorization'] = token;
} else {
axios.defaults.headers.common['Authorization'] = null;
/*if setting null does not remove `Authorization` header then try
delete axios.defaults.headers.common['Authorization'];
*/
}
})();
Now you no longer need to attach token manually to every request. You can place the above function in the file which is guaranteed to be executed every time (e.g: File which contains the routes).
Create instance of axios:
// Default config options
const defaultOptions = {
baseURL: <CHANGE-TO-URL>,
headers: {
'Content-Type': 'application/json',
},
};
// Create instance
let instance = axios.create(defaultOptions);
// Set the AUTH token for any request
instance.interceptors.request.use(function (config) {
const token = localStorage.getItem('token');
config.headers.Authorization = token ? `Bearer ${token}` : '';
return config;
});
Then for any request the token will be select from localStorage and will be added to the request headers.
I'm using the same instance all over the app with this code:
import axios from 'axios';
const fetchClient = () => {
const defaultOptions = {
baseURL: process.env.REACT_APP_API_PATH,
method: 'get',
headers: {
'Content-Type': 'application/json',
},
};
// Create instance
let instance = axios.create(defaultOptions);
// Set the AUTH token for any request
instance.interceptors.request.use(function (config) {
const token = localStorage.getItem('token');
config.headers.Authorization = token ? `Bearer ${token}` : '';
return config;
});
return instance;
};
export default fetchClient();
The best solution to me is to create a client service that you'll instantiate with your token an use it to wrap axios.
import axios from 'axios';
const client = (token = null) => {
const defaultOptions = {
headers: {
Authorization: token ? `Token ${token}` : '',
},
};
return {
get: (url, options = {}) => axios.get(url, { ...defaultOptions, ...options }),
post: (url, data, options = {}) => axios.post(url, data, { ...defaultOptions, ...options }),
put: (url, data, options = {}) => axios.put(url, data, { ...defaultOptions, ...options }),
delete: (url, options = {}) => axios.delete(url, { ...defaultOptions, ...options }),
};
};
const request = client('MY SECRET TOKEN');
request.get(PAGES_URL);
In this client, you can also retrieve the token from the localStorage / cookie, as you want.
Similarly, we have a function to set or delete the token from calls like this:
import axios from 'axios';
export default function setAuthToken(token) {
axios.defaults.headers.common['Authorization'] = '';
delete axios.defaults.headers.common['Authorization'];
if (token) {
axios.defaults.headers.common['Authorization'] = `${token}`;
}
}
We always clean the existing token at initialization, then establish the received one.
The point is to set the token on the interceptors for each request
import axios from "axios";
const httpClient = axios.create({
baseURL: "http://youradress",
// baseURL: process.env.APP_API_BASE_URL,
});
httpClient.interceptors.request.use(function (config) {
const token = localStorage.getItem('token');
config.headers.Authorization = token ? `Bearer ${token}` : '';
return config;
});
If you want to call other api routes in the future and keep your token in the store then try using redux middleware.
The middleware could listen for the an api action and dispatch api requests through axios accordingly.
Here is a very basic example:
actions/api.js
export const CALL_API = 'CALL_API';
function onSuccess(payload) {
return {
type: 'SUCCESS',
payload
};
}
function onError(payload) {
return {
type: 'ERROR',
payload,
error: true
};
}
export function apiLogin(credentials) {
return {
onSuccess,
onError,
type: CALL_API,
params: { ...credentials },
method: 'post',
url: 'login'
};
}
middleware/api.js
import axios from 'axios';
import { CALL_API } from '../actions/api';
export default ({ getState, dispatch }) => next => async action => {
// Ignore anything that's not calling the api
if (action.type !== CALL_API) {
return next(action);
}
// Grab the token from state
const { token } = getState().session;
// Format the request and attach the token.
const { method, onSuccess, onError, params, url } = action;
const defaultOptions = {
headers: {
Authorization: token ? `Token ${token}` : '',
}
};
const options = {
...defaultOptions,
...params
};
try {
const response = await axios[method](url, options);
dispatch(onSuccess(response.data));
} catch (error) {
dispatch(onError(error.data));
}
return next(action);
};
Sometimes you get a case where some of the requests made with axios are pointed to endpoints that do not accept authorization headers. Thus, alternative way to set authorization header only on allowed domain is as in the example below. Place the following function in any file that gets executed each time React application runs such as in routes file.
export default () => {
axios.interceptors.request.use(function (requestConfig) {
if (requestConfig.url.indexOf(<ALLOWED_DOMAIN>) > -1) {
const token = localStorage.token;
requestConfig.headers['Authorization'] = `Bearer ${token}`;
}
return requestConfig;
}, function (error) {
return Promise.reject(error);
});
}
Try to make new instance like i did below
var common_axios = axios.create({
baseURL: 'https://sample.com'
});
// Set default headers to common_axios ( as Instance )
common_axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
// Check your Header
console.log(common_axios.defaults.headers);
How to Use it
common_axios.get(url).......
common_axios.post(url).......
export const authHandler = (config) => {
const authRegex = /^\/apiregex/;
if (!authRegex.test(config.url)) {
return store.fetchToken().then((token) => {
Object.assign(config.headers.common, { Authorization: `Bearer ${token}` });
return Promise.resolve(config);
});
}
return Promise.resolve(config);
};
axios.interceptors.request.use(authHandler);
Ran into some gotchas when trying to implement something similar and based on these answers this is what I came up with. The problems I was experiencing were:
If using axios for the request to get a token in your store, you need to detect the path before adding the header. If you don't, it will try to add the header to that call as well and get into a circular path issue. The inverse of adding regex to detect the other calls would also work
If the store is returning a promise, you need to return the call to the store to resolve the promise in the authHandler function. Async/Await functionality would make this easier/more obvious
If the call for the auth token fails or is the call to get the token, you still want to resolve a promise with the config
I am using GraphQL Code Generator with React Query, this is my codegen.yml:
overwrite: true
schema: http://localhost:4000/graphql
generates:
src/lib/__generated__/graphql.ts:
documents:
- "**/graphql/**/*.graphql"
- "!mysqldata/**"
plugins:
- add:
content: &comment "/* DO NOT EDIT! this file was generated by graphql-codegen */\n/* eslint-disable */"
- add:
placement: append
content: "export { fetcher }"
- typescript
- typescript-operations
- typescript-react-query
config:
fetcher:
endpoint: "`${process.env.NEXT_PUBLIC_API_URL}/graphql`"
fetchParams:
credentials: include
headers:
Content-Type: application/json
This generates the following fetcher:
function fetcher<TData, TVariables>(query: string, variables?: TVariables) {
return async (): Promise<TData> => {
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/graphql` as string, {
method: "POST",
credentials: "include",
headers: {"Content-Type":"application/json"},
body: JSON.stringify({ query, variables }),
});
const json = await res.json();
if (json.errors) {
const { message } = json.errors[0];
throw new Error(message);
}
return json.data;
}
}
NEXT_PUBLIC_API_URL refers to an external GraphQL API.
In my Next.js application I tried to make use of nextjs-auth0 and auth0-react.
nextjs-auth0 allows me to access Auth0's ID token from Next.js API routes:
export default (req: NextApiRequest, res: NextApiResponse) => {
const session = getSession(req, res)
const idToken = session?.idToken
while auth0-react allows me to get the token client side:
const claims = await auth0.getIdTokenClaims();
const idToken = claims.__raw;
The problem is that because of these abstractions, I cannot figure out a way to include this token in requests to my GraphQL endpoint like:
headers: {
authorization: `Bearer ${session?.idToken}`,
},
After I posted a feature request to include the ID token inside a cookie, I figured the "appSession" cookie that's set by nextjs-auth0 is an encrypted token which includes the ID token, I implemented custom server logic using the nextjs-auth0 source code as reference:
type DecodedToken = Record<"idToken" | "token_type", string>
const API_BASE_URL = "https://example.com"
const BYTE_LENGTH = 32
const ENCRYPTION_INFO = "JWE CEK"
const HASH = "SHA-256"
const alg = "dir"
const enc = "A256GCM"
/**
* Derives appropriate sized keys from provided secret random string/passphrase using
* HKDF (HMAC-based Extract-and-Expand Key Derivation Function) defined in RFC 8569
* #see https://tools.ietf.org/html/rfc5869
*/
function deriveKey(secret: string) {
return hkdf(secret, BYTE_LENGTH, { info: ENCRYPTION_INFO, hash: HASH })
}
export const meQueryField = queryField("me", {
type: "User",
async resolve(_, __, ctx) {
const jwe = ctx.request.cookies["appSession"]
if (!jwe) {
return null
}
// eslint-disable-next-line #typescript-eslint/no-non-null-assertion
const octKey = JWK.asKey(deriveKey(process.env["AUTH0_SECRET"]!))
const { cleartext } = JWE.decrypt(jwe, octKey, {
complete: true,
contentEncryptionAlgorithms: [alg],
keyManagementAlgorithms: [enc],
})
const { idToken, token_type: tokenType } = JSON.parse(
cleartext.toString()
) as DecodedToken
const response = await fetch(`${API_BASE_URL}/users/me`, {
headers: {
Authorization: `${tokenType} ${idToken}`,
},
})
const user = (await response.json()) as Response
return {
id: user.data.id,
...
}
},
})
It's not pretty but it works. AUTH0_SECRET is the same secret that's used to encrypt the token in nextjs-auth0
I have managed to make this run: How to modify axios instance after exported it in ReactJS?
And it looks like this:
import axios from 'axios';
import constants from '../constants.js';
import Cookies from 'js-cookie';
const API = axios.create({
baseURL: `${constants.urlBackend}`,
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
API.interceptors.request.use(
config => {
var accesstoken = Cookies.get('accesstoken');
if (accesstoken) {
config.headers.Authorization = `Bearer ${accesstoken}`;
} else {
delete API.defaults.headers.common.Authorization;
}
return config;
},
error => Promise.reject(error)
);
export default API;
And this is an example usage
getUserList() {
API.get('/userlist')
.then(response => {
this.setState({
userList: response.data
}, () => {
console.log(this.state.userList)
});
})
}
But now im confused because I dont understand how to use this with a post so I can pass some data to it, similar to this
axios({
method: 'post',
url: constants.urlBackend + "/register",
data: qs.stringify({ email, password }),
headers: {
'content-type': 'application/x-www-form-urlencoded;charset=utf-8'
}
})
But using the above object.
API.post('/user/update/'+this.state.rowId).then(response => {
//some sort of body {email,password}
})
Have you tried
API.post(
'/user/update/' + this.state.rowId, {
email,
password
}).then(response => {})
I am using a JWT Token auth system, and when I login I get the token like this:
axios.post('/login', data)
.then(response => {
localStorage.setItem('token', response.data.token);
});
This works well and the token is saved in localStorage. However, the token is not included in the later requests. The Authorization header is Bearer null.
This is how I set up my global axios object.
window.axios = axios.create({
baseURL: '/api/',
timeout: 10000,
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-TOKEN': document.head.querySelector('meta[name="csrf-token"]').content,
'Authorization': 'Bearer ' + localStorage.getItem('token')
}
});
If I refresh the site, the token is set, and is used properly.
Edit:
I got it to work by removing the Authorization header from the create() method and instead using window.axios.defaults.headers.common['Authorization']. But now the same problem appears with Laravel Echo. I create the instance like this:
window.Echo = new Echo({
broadcaster: 'pusher',
key: 'xxx',
cluster: 'eu',
encrypted: true,
namespace: 'xxx',
auth: {
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('token')
}
}
});
And I update the header like this:
window.setAuthToken = (token) => {
window.axios.defaults.headers.Authorization = 'Bearer ' + token;
window.Echo.options.auth.headers.Authorization = 'Bearer ' + token;
localStorage.setItem('token', token);
}
The axios header is successfully updated, but not Echo.
Use axios interceptors for this purpose. It will run for every request call.
Better to keep axios methods in a separate file and make call to it than using it directly in all components. This way we can replace axios with another library if we want with minimal effort. Here's what I'm doing in my project.
import axios from "axios";
import AuthService from "./auth";
import config from '../config'
const instance = axios.create({
baseURL: config.apiServer.url,
timeout: config.apiServer.timeout
});
instance.interceptors.request.use(
config => {
const token = AuthService.getToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
error => Promise.reject(error)
);
const ApiService = {
get(url) {
return instance.get(url)
.then(res => res)
.catch(reason => Promise.reject(reason));
},
post(url, data) {
return instance.post(url, data)
.then(res => res)
.catch(reason => Promise.reject(reason));
},
awaitAll() {
return axios.all(Array.from(arguments))
.then(axios.spread((...responses) => responses))
.catch(reasons => Promise.reject(reasons));
}
};
export default ApiService;
Now to use it in a component:
ApiService.get(YOUR_GET_URL)
.then(res => {
Console.log(res);
))
.catch(reason => {
console.log(reason);
})
The problem is that your are using localStorage.getItem('token') at page load. When you are setting it in localStorage, you have to update it in axios header.
window.axios = axios.create({
baseURL: '/api/',
timeout: 10000,
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-TOKEN': document.head.querySelector('meta[name="csrf-token"]').content,
'Authorization': 'Bearer ' + localStorage.getItem('token')
}
});
axios.post('/login', data)
.then(response => {
localStorage.setItem('token', response.data.token);
window.axios.defaults.headers.common['Authorization'] = 'Bearer ' + localStorage.getItem('token');
});
I faced the same problem before and I found out that the file that contains my axios config was being loaded at the time of storing the token, so it was accessing it before it is stored.
The solution is, in axios config:
const axiosInstance = axios.create({
baseURL: `${API_BASE_URL}`,
headers: {
Accepted: 'appication/json',
'Content-Type': 'application/json',
},
});
axiosInstance.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.authorization = token;
}
return config;
},
(error) => Promise.reject(error),
);
export default axiosInstance;
After that, use this instance where you need to make a request.