Add additional headers in Axios create function - javascript

I have this already made function to send requests from Axios,
export const http = axios.create({
baseURL: 'https://someurl/api',
headers: {
'Content-type': 'application/json',
Accept: 'application/json',
},
});
So I can call this method anywhere in my application,
import {http} from 'src/helpers/HttpHelper';
http
.post(
'/users',
{name: 'mishen'},
)
.then(res => console.log(res))
.catch(error => console.log());
Since there are protected routes too which require a bearer token I tried to do something like this inside my component,
import {useContext} from 'react';
import {http} from 'src/helpers/HttpHelper';
const MyComponent = () => {
const {userToken} = useContext(AuthContext);
const signUpUser = () => {
http
.post(
'/app_user_role_selection',
{name: 'mishen'},
{headers: {Authorization: `Bearer ${userToken}`}}
)
.then(res => console.log(res))
.catch(error => console.log());
}
...
}
However, this is not working.

You can use axios interceptors.
export const http = axios.create({
baseURL: "url",
headers: {
"Content-type": "application/json",
Accept: "application/json"
}
});
http.interceptors.request.use(async (config) => {
const value = await AsyncStorage.getItem("your key");
if (value !== null) {
config.headers["Authorization"] = `Bearer ${value}`;
}
return config;
});
const MyComponent = () => {
const signUpUser = () => {
http
.post(
"/app_user_role_selection",
{ name: "mishen" }
)
.then((res) => console.log(res))
.catch((error) => console.log());
};
};

Related

Uncaught (in promise) TypeError: res.map is not a function

I'm trying to fetch a list of departments from an url in a react native application
import React,{ useState,useEffect} from 'react';
import { StyleSheet, LogBox,View,Text } from 'react-native';
export default function App() {
var [department,setDepartment]=useState([])
const token = /* my token here */
const getDepartments=()=>{
const url = /*my api's url here*/
return fetch(url, {
method: 'GET',
headers: { "Authorization": "Bearer" + token ,
'Accept': 'application/json',
'Content-Type':'application/json'
}
})
.then(response => response.json())
.then(data=>console.log(data)) // returns the correct data
.catch(error => console.error(error))
}
const getdepartment = async () => {
await getDepartments().then((res) => //here lays the problem
{res.map((p, key) => {
department.push({
name: p.name,
id: p.id,
});
});
});
};
useEffect(() => {
getdepartment();
}, []);
return (
<View>
<Text>
{department[0]}
</Text>
</View>
)
}
here res in the getdepartment() function is undefined despite the getDepartments() function returning correct data from the url
You are not returning a value from getDepartments, just a simple console.log.
You can convert the function in async/await:
const getDepartments = async () => {
const url = /*my api's url here*/
try {
const response = await fetch(url, {
method: 'GET',
headers: { "Authorization": "Bearer" + token ,
'Accept': 'application/json',
'Content-Type':'application/json'
}
})
return await response.json();
} catch(e){
// error
}
}
or return a value from your function:
const getDepartments=()=>{
const url = /*my api's url here*/
return fetch(url, {
method: 'GET',
headers: { "Authorization": "Bearer" + token ,
'Accept': 'application/json',
'Content-Type':'application/json'
}
})
.then(response => response.json())
.catch(error => console.error(error))
}
If you are returning the result of the fetch then just return the result obtained from it, the issue is along with fetch, the response is also handled and the complete thing post to that is being returned which is not the result so you just need to skip this line .then(data=>console.log(data))
const getDepartments=()=>{
const url = /*my api's url here*/
return fetch(url, {
method: 'GET',
headers: { "Authorization": "Bearer" + token ,
'Accept': 'application/json',
'Content-Type':'application/json'
}
}).then(response => response.json()).catch(error =>
console.error(error))
}
// Here after fetching the result you can map the data
const getdepartment = async () => {
await getDepartments().then((res) =>
{res.map((p, key) => {
department.push({
name: p.name,
id: p.id,
});
});
});
};

Iterate through promise object in react component

How can I iterate through the promise object I have returned from my API call.
const [productData, setProductData] = useState(null)
useEffect(() => {
getProductdata()
}, [])
async function getProductdata(){
const secret = "SECRET"
const request = await fetch(`https://app.myapi.com/api/products/id`, {
headers: {
'Authorization': `Basic ${btoa(secret)}`,
'Accept': 'application/json'
}
}).then((request)=>{setProductData(request.json())})
}
console.log("pdata",productData)
If you're using Promise with then then you should do as:
function getProductdata() {
const secret = "SECRET";
fetch(`https://app.myapi.com/api/products/id`, {
headers: {
Authorization: `Basic ${btoa(secret)}`,
Accept: "application/json",
},
})
.then((res) => res.json())
.then(data => setProductData(data);
}
console.log("pdata", productData);
Just await the promise before you call setProductData:
const request = await fetch(`https://app.myapi.com/api/products/id`, {
headers: {
'Authorization': `Basic ${btoa(secret)}`,
'Accept': 'application/json'
}
})
const json = await request.json();
setProductData(json);

How prevent fetch request before auth token is received in React Js?

I have a separate fetch request function that logins user and saves auth token to localStorage, then my data request fetch should be send with that saved token bearer, but data fetch doesn't wait for token and receives Unauthorized access code.
My data request fetch looks like this :
// to check for fetch err
function findErr(response) {
try {
if (response.status >= 200 && response.status <= 299) {
return response.json();
} else if (response.status === 401) {
throw Error(response.statusText);
} else if (!response.ok) {
throw Error(response.statusText);
} else {
if (response.ok) {
return response.data;
}
}
} catch (error) {
console.log("caught error: ", error);
}
}
const token = JSON.parse(localStorage.getItem("token"));
// actual fetch request
export async function getData() {
const url = `${URL}/data`;
var obj = {
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: "Bearer " + `${token}`,
},
};
const data = await fetch(url, obj)
.then((response) => findErr(response))
.then((result) => {
return result.data;
});
return data;
}
My fetch requests are in a different js file, I'm importing them in my components like this:
import React, { useState, useEffect } from "react";
function getInfo() {
const [info, setInfo] = useState()
const importGetDataFunc = async () => {
const data = await getData();
setInfo(data);
};
useEffect(() => {
importGetDataFunc();
}, []);
return (
<div>
</div>
)
}
export default getInfo
Now when I go to the getInfo component after login at first fetch request returns 401, but after I refresh the page fetch request goes with token bearer and data gets returned. My problem is that I don't know how to make getData() fetch request to wait until it gets token from localStorage or retry fetch request on 401 code. I tried to implement if statement like this
useEffect(() => {
if(token){
importGetDataFunc();
}
}, []);
where useEffect would check if token is in localStorage and only then fire fetch request, but it didn't work. Any help on how I can handle this would be greatly appreciated.
You are close. You need to add token as a dependency to your useEffect. Also, you need to move your token fetching logic into your component.
Something like this should work:
import React, { useState, useEffect } from "react";
function getInfo() {
const [info, setInfo] = useState()
const token = JSON.parse(localStorage.getItem("token"));
const importGetDataFunc = async () => {
const data = await getData();
setInfo(data);
};
useEffect(() => {
if(token) {
importGetDataFunc(token);
}
}, [token]);
return (
<div>
</div>
)
}
export default getInfo
You can also modify your importGetDataFunc to receive the token as a parameter.
const importGetDataFunc = async (token) => {
const data = await getData(token);
setInfo(data);
};
export async function getData(token) {
const url = `${URL}/data`;
var obj = {
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: "Bearer " + `${token}`,
},
};
const data = await fetch(url, obj)
.then((response) => findErr(response))
.then((result) => {
return result.data;
});
return data;
}
What actually helped me is to make a function to check for a token inside get fetch request, like this:
export const findToken = () => {
const token =localStorage.getItem("token")
return token;
};
export async function getData(token) {
const url = `${URL}/data`;
var obj = {
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: "Bearer " + `${findToken()}`,
},
};
const data = await fetch(url, obj)
.then((response) => findErr(response))
.then((result) => {
return result.data;
});
return data;
}

Issue with axios-auth-refresh

I'm trying to implement refresh-token from react. I'm using this library axios-auth-refresh which seems to work very fine except for one API.
// api.js
import Axios from "axios";
import Cookies from 'js-cookie'
import { TOKEN_COOKIE_NAME, REFRESH_TOKEN_COOKIE_NAME } from '../constants/constants';
import createAuthRefreshInterceptor from 'axios-auth-refresh';
const api = Axios.create({
baseURL: process.env.REACT_APP_BACKEND_URL,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
const refreshAuthLogic = async (failedRequest) => {
const refresh_token = Cookies.get(REFRESH_TOKEN_COOKIE_NAME);
// if(!refresh_token) return;
console.log(refresh_token);
const tokenRefreshResponse = await api.post('auth/createtoken', {
}, {
headers: {'Authorization': 'Bearer ' + refresh_token},
validateStatus: () => true
});
console.log(tokenRefreshResponse);
if(tokenRefreshResponse.data.statusCode === 401 || tokenRefreshResponse.data.statusCode === 403) {
Cookies.remove(REFRESH_TOKEN_COOKIE_NAME);
if(!window.location.href.includes('login')) {
window.location.href = "http://localhost:3000/login";
}
return;
}
const access_token = tokenRefreshResponse.data.access_token;
Cookies.set(TOKEN_COOKIE_NAME, access_token, { expires: 60 })
api.defaults.headers.Authorization = `Bearer ${access_token}`
failedRequest.response.config.headers['Authorization'] = 'Bearer ' + access_token;
}
// Instantiate the interceptor (you can chain it as it returns the axios instance)
createAuthRefreshInterceptor(api, refreshAuthLogic);
export default api;
The following api call does NOT repeat in case 401 is returned:
const fetchUsers = async () => {
const { data } = await api.get(`users/`, {params: {tripUsers: true}}, {
validateStatus: (status) => status !== 401 && status !== 403
})
setUsers(data);
}
useEffect(() => {
fetchUsers();
}, [])
The following api call DOES repeat in case 401 is returned:
const fetchProfile = async () => {
const { data } = await api.get(`/users/${user.userId}`, {}, {
validateStatus: (status) => status !== 401 && status !== 403
})
const {statusCode, message} = data;
console.log(data);
if(!statusCode) {
console.log(data);
setState(data);
}
}
useEffect(() => {
fetchProfile();
}, [])
Please help.
After spending some time on this issue, I decided to create a generic API caller rather than using axios interceptors or any other library. Here's my generic axios API caller. It can still be improved, but the idea is to call the API again with a new token if the first token is expired.
// api.js
import Axios from "axios";
import Cookies from 'js-cookie'
import { TOKEN_COOKIE_NAME, REFRESH_TOKEN_COOKIE_NAME } from '../constants/constants';
const api = Axios.create({
baseURL: process.env.REACT_APP_BACKEND_URL,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
export const callApi = async (method, url, params, other) => {
const validateStatus =
url === 'auth/login' ? () => true : (status) => status !== 401 && status !== 403
const options = {
url,
method,
validateStatus,
...other
}
options[method === 'GET' ? 'params' : 'data'] = params;
console.log(options);
try {
const data = await api(options);
return Promise.resolve(data);
} catch (err) {
console.log(err.response.status);
if (err && err.response && err.response.status === 401) {
return performTokenRefresh(options);
} else {
return Promise.reject(err);
}
}
};
const performTokenRefresh = async (options) => {
const refresh_token = Cookies.get(REFRESH_TOKEN_COOKIE_NAME);
if(!refresh_token) return {};
const tokenRefreshResponse = await api.post('auth/createtoken', {
}, {
headers: {'Authorization': 'Bearer ' + refresh_token},
validateStatus: () => true
});
if(tokenRefreshResponse.data.statusCode === 401 || tokenRefreshResponse.data.statusCode === 403) {
Cookies.remove(REFRESH_TOKEN_COOKIE_NAME);
if(!window.location.href.includes('login')) {
window.location.href = "http://localhost:3000/login";
}
return {};
}
const access_token = tokenRefreshResponse.data.access_token;
Cookies.set(TOKEN_COOKIE_NAME, access_token, { expires: 60 })
api.defaults.headers.Authorization = `Bearer ${access_token}`
return api(options);
}
export default api;

How can I mock functions in a imported module using Jest?

I have a module called authProvider.js which I want to mock when I'm testing one of my functions in api.js.
I have set "automock": true in my jest config.
This is my structure
src
|-auth
| |-__mocks__
| | |-authProvider.js
| |-authProvider.js
|-utils
|-api.js
|-api.test.js
This is what I have tried so far but only having success with the first test case. I'm not sure how to set up the mocking in the second test case...
api.test.js
import { mockAuthProvider } from "../auth/__mocks__/authProvider";
import { getDefaultHeaders, getValueIndexByColumnName } from './api';
describe('API utils', () => {
describe('getDefaultHeaders', () => {
it('Not authenticated', async () => {
expect(await getDefaultHeaders()).toEqual({
'Content-Type': 'application/json'
});
});
it('Authenticated', async () => {
mockAuthProvider.getAccount.mockImplementationOnce(() =>
Promise.resolve({user: 'test'})
);
const headers = await getDefaultHeaders();
expect(mockAuthProvider.getAccount).toBeCalled();
expect(headers).toEqual({
Authorization: 'Bearer abc123',
'Content-Type': 'application/json'
});
});
});
});
api.js
import { authProvider } from '../auth/authProvider';
import settings from '../settings';
export async function getDefaultHeaders() {
const account = await authProvider.getAccount();
const authenticationParameters = {
scopes: ['api://' + settings.AD_CLIENT_ID + '/login'],
redirectUri: window.location.origin + '/auth.html'
};
let token;
if (account) {
try {
token = await authProvider.acquireTokenSilent(authenticationParameters);
} catch (error) {
token = await authProvider.acquireTokenPopup(authenticationParameters);
}
}
if (token) {
return {
Authorization: `Bearer ${token.accessToken}`,
'Content-Type': 'application/json'
}
}
return {
'Content-Type': 'application/json'
}
}
__ mocks __/authProvider.js
const mockAuthProvider = {
getAccount: jest.fn(),
acquireTokenSilent: jest.fn(),
acquireTokenPopup: jest.fn()
};
module.exports = {
mockAuthProvider
};
Error message
Expected number of calls: >= 1
Received number of calls: 0
18 | const headers = await getDefaultHeaders();
19 |
> 20 | expect(mockAuthProvider.getAccount).toBeCalled();
| ^
UPDATE
I added a file to mock the whole module that exports the auth provider, but still not the best way to solve it I think. I'm having difficulties using it in multiple test cases since I need to specify the return values in a specific order.
Is there a better way to solve this issue?
__ mocks __/react-aad-msal.js
import React from 'react';
const errorObj = {
message: 'Some error'
};
export const mockGetAccount = jest.fn()
.mockReturnValueOnce(null) // Not authenticated
.mockReturnValueOnce({user: 'test'}) // Authenticated silent
.mockReturnValueOnce({user: 'test'}); // Authenticated popup
export const mockAcquireTokenSilent = jest.fn()
.mockReturnValueOnce({accessToken: 'abc123'}) // Authenticated silent
.mockRejectedValueOnce(errorObj); // Authenticated popup
export const mockAcquireTokenPopup = jest.fn()
.mockReturnValueOnce({accessToken: 'abc123'}); // Authenticated popup
export const MsalAuthProvider = jest.fn(() => ({
getAccount: mockGetAccount,
acquireTokenSilent: mockAcquireTokenSilent,
acquireTokenPopup: mockAcquireTokenPopup
}));
export const AuthenticationState = {
Authenticated: 'Authenticated',
Unauthenticated: 'Unauthenticated'
};
export const LoginType = {
Popup: 'popup'
};
export const AuthenticationActions = {
Initializing: 'Initializing',
Initialized: 'Initialized',
AcquiredIdTokenSuccess: 'AcquiredIdTokenSuccess',
AcquiredAccessTokenSuccess: 'AcquiredAccessTokenSuccess',
AcquiredAccessTokenError: 'AcquiredAccessTokenError',
LoginSuccess: 'LoginSuccess',
LoginError: 'LoginError',
AcquiredIdTokenError: 'AcquiredIdTokenError',
LogoutSucc: 'LogoutSucc',
AuthenticatedStateChanged: 'AuthenticatedStateChanged'
};
export const AzureAD = ({children}) => <div>{children}</div>;
The new api.test.js looks like this, note that the order of the tests now is important since the return values from the mock are in a fixed order.
import { getDefaultHeaders, axiosCreate, getValueIndexByColumnName } from './api';
describe('API utils', () => {
describe('getDefaultHeaders', () => {
it('Not authenticated', async () => {
const headers = await getDefaultHeaders();
expect(headers).toEqual({
'Content-Type': 'application/json'
});
});
it('Authenticated silent', async () => {
const headers = await getDefaultHeaders();
expect(headers).toEqual({
Authorization: 'Bearer abc123',
'Content-Type': 'application/json'
});
});
it('Authenticated popup', async () => {
const headers = await getDefaultHeaders();
expect(headers).toEqual({
Authorization: 'Bearer abc123',
'Content-Type': 'application/json'
});
});
});
describe('axiosCreate', () => {
it('Create axios API base', () => {
expect(axiosCreate()).toBeTruthy();
});
});
describe('getValueIndexByColumnName', () => {
it('Invalid input data', () => {
expect(getValueIndexByColumnName([], null)).toEqual(null);
expect(getValueIndexByColumnName(['column1'], null)).toEqual(-1);
});
it('Valid input data', () => {
expect(getValueIndexByColumnName(['column1'], 'column')).toEqual(-1);
expect(getValueIndexByColumnName(['column1'], 'column1')).toEqual(0);
expect(getValueIndexByColumnName(['column1', 'column2', 'column3'], 'column2')).toEqual(1);
});
});
});

Categories