All the endpoints in the backend require Authorization header. This header is stored in SecureStore.
Problem Statement
I want to load the Authorization header ( JWT Token ), for every API call after logging in.
Now this requires an async operation i.e.authStorage.getToken.
This is my client.js ( the apisauce client ).
client.js
import { create } from "apisauce";
import authStorage from "../auth/storage";
import IP from "../config/network";
const restoreToken = async () => {
return await authStorage.getToken("idToken");
};
const apiClient = (auth_token = "") =>
create({
baseURL: "http://" + IP + ":8990",
headers: { Authorization: auth_token }, // This I've added later
});
export default apiClient;
This is the PostsApi which uses apiClient to make the calls.
PostsApi.js
import apiClient from "./client";
const endpoint = "/api/";
const bookmarkEndpoint = "/bookmark/";
const getPosts = (last_id = 0, limit = 10) => {
return apiClient.get(endpoint + "?last_id=" + last_id + "&limit=" + limit);
};
const toggleBookmark = (item_id) => {
return apiClient.get(bookmarkEndpoint + "?item_id=" + item_id);
};
export default {
getPosts,
toggleBookmark,
};
My Understanding
I understand that if I can add the header in client.js itself, it would be injected everytime there's an API call.
I've tried :
const restoreToken = async () => {
return await authStorage.getToken("idToken");
};
But I am not sure how to call this async operation in client.js
Bonus Question
This token ( idToken ) would be reloaded every hour, so it's best to get the token from SecureStore everytime instead of saving it once.
Thanks.
Accepted answer and what worked for me
Worked for me
apisauce's setHeader : Documented here
Accepted answer is a detailed drilling of the axios setting up of headers. So if someone's using axios client directly they can see the accepted answer else, if you're an apisauce user, use the setHeader functionality provided with the library.
Cheers.
You will have to store your token with the state (can be redux or local state).
During save/refresh/reload the token, you will have set headers of the HTTP client.
You can set header using below command (example)
export const setAuthToken = (token) => {
apiClient.defaults.headers.common['Authorization'] = ''
delete apiClient.defaults.headers.common['Authorization']
if (token) {
apiClient.defaults.headers.common['Authorization'] = `Bearer ${token}`
}
}
Call the above function to set a token during reload/refresh/creation of token.
const restoreToken = async () => {
return await authStorage.getToken("idToken").then(token => setAuthToken(token));
};
Related
In the code below, I will call LoginAPI for authorization and writes token as a state(Login.e2e.ts ).By the way using axios interceptors.request in my axios.ts file.
My question is;
If I use below code logic, when I send request with customAxios in my project,everytime await LoginAPI.API.Signin.run() will run for every API request. Soon,I can have 100 API call. I don t want run every time await LoginAPI.API.Signin.run(),because I can take 429 error.
The new logic should be like this;
I want to take a token first and then use it until it expires. If the token expired then send a new request and get a new token. How can do this using JavaScript or TypeScript?
This is my Login.e2e.ts file
import api from "api/core"
import { expect } from "#playwright/test";
export const LoginAPI = {
States: {
token: {} as string
},
API: {
Signin: {
notes: "user login",
run: async () => {
let res: any = await api.test.LoginPost(process.env.NAME, process.env.PASS)
LoginAPI.States.token = res.data.token
expect(res.status).toBe(200)
},
},
},
};
This is my axios.ts file
import axios from "axios";
import { LoginAPI } from "../playwright/tests/login/login.api";
const customAxios = axios.create({
baseURL: process.env.ENV === '1' ? "https://test1" : process.env.ENV === '2' ? "https://test2" : "https://test3",
});
customAxios.interceptors.request.use(
async (config) => {
await LoginAPI.API.Signin.run()
if (config.headers) {
config.headers['Authorization'] = `Bearer ${LoginAPI.States.token}`;
return config;
}
return config;
},
(error) => {
Promise.reject(error);
}
);
export default customAxios
take a token first and then use it until it expires. If the token expired then send a new request and get a new token.The above code should be changed the this logic
I would suggest you to log in once and get cookies from the browser.context() and save them as JSON file then use this cookie state/session for the rest of the test. That way you won't have to log in every time for new tests or test suites.
More information using storageState(options) here in official docs.
Example of using storageState(options) in your global setup and teardown here in official docs.
I am currently working on social media mern stack react app. I am using node js and express as my backend services , also using mongoose to store my data and axios and redux thunk which connect the backend to the front end. Till now I had no issue recieving and sending data to the server. Right now I am trying to create search post get request ,base on a keyword the user entered. The issue with it, that when I am sending the keyword to the server instead of recieving the string it gets undefined value, like redux thunk not sending anything. I will be very thankful if someone could help me with that. I am watching the code over and over again and can't find out the reason for that.
My post controller class(I copied only the relevant function):
import express from "express";
const app = express();
import Post from "../model/PostModel.js";
import ErrorHandlng from "../utilities/ErrorHandling.js";
import bodyParser from "body-parser";
import catchAsync from "../utilities/CatchAsync.js";
import User from "../model/UserModel.js";
app.use(express.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
export const getPosts = catchAsync(async (req, res, next) => {
const data = req.body.keyword;
const page = parseInt(req.query.page || "0");
const PAGE_SIZE = 20;
const query = new RegExp(data, "i");
const total = await Post.countDocuments({});
const posts = await Post.find({ $or: [{ title: query }, { content: query }] })
.limit(PAGE_SIZE)
.skip(PAGE_SIZE * page);
if (!posts) {
return next(new ErrorHandlng("No posts were found", 400));
}
res.status(200).json({
status: "success",
data: {
totalPages: Math.ceil(total / PAGE_SIZE),
posts,
},
});
});
My api class(front end,copied only the calling for that specific get request):
import axios from "axios";
const baseURL = "http://localhost:8000";
axios.defaults.withCredentials = true;
const API = axios.create({
baseURL,
credentials: "include",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
});
export const getPostsByKeyword = (keyword, page) =>
API.get(`/post/getPostsByKey?page=${page}`, keyword);
Post slice class:
export const fetchPostsByKeyWord = createAsyncThunk(
"post/getKeyword",
async ({ keyword, page }, { fulfillWithValue, rejectWithValue }) => {
try {
const response = await api.getPostsByKeyword(keyword, page);
if (response.statusCode === "400") {
throw new Error("There are no available posts");
}
const fetchData = await response.data.data.posts;
const totalPages = await response.data.data.totalPages;
return fulfillWithValue({ fetchData, totalPages });
} catch (err) {
console.log(err.response.message);
}
}
);
const initialState = { status: "undefined" };
const PostSlice = createSlice({
name: "post",
initialState,
reducers: {},
extraReducers: {},
});
export const postActions = PostSlice.actions;
export default PostSlice;
Calling the backend:
dispatch(fetchPostsByKeyWord({ keyword, page }))
.unwrap()
.then((originalPromiseResults) => {
console.log("thte " + " " + originalPromiseResults.totalPages);
console.log("The data is" + originalPromiseResults.fetchData);
setTotalPages(originalPromiseResults.totalPages);
})
.catch((err) => {
console.log(err.message);
});
As you can see I have not copied the whole code, I copied only the parts that are relevants for the question.
Browsers cannot currently send GET requests with a request body. XMLHttpRequest (which Axios uses) will ignore it and fetch() will trigger an error.
See also HTTP GET with request body for extra discussion on why trying this might be a bad idea.
You should instead pass everything required in the query string, preferably via the params option so it is correctly encoded...
export const getPostsByKeyword = (keyword, page) =>
API.get("/post/getPostsByKey", { params: { page, keyword } });
and grab the data via req.query server-side.
const { page, keyword } = req.query;
With vanilla JS, you can use URLSearchParams to construct the query string...
const params = new URLSearchParams({ page, keyword });
// XHR
const xhr = new XMLHttpRequest();
xhr.open("GET", `/post/getPostsByKey?${params}`);
// Fetch
fetch(`/post/getPostsByKey?${params}`); // GET is the default method
Your Axios instance creation could also be a lot simpler...
Axios is usually quite good at setting the correct content-type header, you don't have to
Your Express app isn't doing any content-negotiation so you don't need to set the accept header
Unless you're actually using cookies (which it doesn't look like), you don't need credential support
const API = axios.create({ baseURL });
I'm using NextJS 12.0.10 with next-redux-wrapper 7.0.5
And Axios custom instance to hold user JWT token saved in local storage and inject it with every request also to interceptors incoming error's in each response
The problem with this is that I simply cannot use the Axios instance inside the Next data fetching methods
Because there is no way to bring user JWT Token from local storage when invoking the request inside the server
Also, I cannot track the request in case of failure and send the refresh token quickly
I tried to use cookies but getStaticProps don't provide the req or resp obj
Should I use getServerSideProps always
axios.js
const axiosInstance = axios.create({
baseURL: baseURL,
timeout: 20000,
headers: {
common: {
Authorization: !isServer()
? localStorage.getItem("access_token")
? "JWT " + localStorage.getItem("access_token")
: null
: null,
accept: "application/json",
},
},
});
login-slice.js
export const getCurrentUser = createAsyncThunk(
"auth/getCurrentUser",
async (_, thunkApi) => {
try {
const response = await axiosInstance.get("api/auth/user/");
await thunkApi.dispatch(setCurrentUser(response.data));
return response.data;
} catch (error) {
if (error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
toast.error(error.message);
return thunkApi.rejectWithValue(error.message);
}
}
);
Page.jsx
export const getStaticProps = wrapper.getStaticProps((store) => async (ctx) => {
try {
await store.dispatch(getCurrentUser());
} catch (e) {
console.log("here", e);
}
return {
props: {},
};
});
Server side rendered technology is a one-way street if you follow the standard practise. You won't get any local details - being it cookies, local store or local states back to the server.
I would let the server build the DOM as much as it makes sense (ie with empty user data) and let the client fetch the data via useEffect.
In my application, I m using React with Redux and axios.
I am trying to refresh my JWT token if needed via axios interceptors.
I am able to intercept the requests, check if my access token is valid, and if it is expired I am able to request a new pair of tokens using my refresh token.
However, after I obtain my new access and refresh token, my app does not continue with the original request, but it stops and tries to render my components with errors (given that I am missing the response from the original request).
I believe there is some error in how I manage my async functions
I configured an axios instance as follows
in axiosHelper.js
import axios from 'axios';
import jwt_decode from "jwt-decode"
import dayjs from 'dayjs'
let authTokens = localStorage.getItem('authTokens') ? JSON.parse(localStorage.getItem('authTokens')) : null
const instance = axios.create({ baseURL: 'http://localhost:8000'});
instance.interceptors.request.use(async (req) => {
if(!authTokens){
authTokens = localStorage.getItem('authTokens') ?
JSON.parse(localStorage.getItem('authTokens')) : null
}
const user = jwt_decode(authTokens.access)
const isExpired = dayjs.unix(user.exp).diff(dayjs()) < 1
if (!isExpired) return req
const {tokens} = await axios.post(`/refresh-url/`, {
refresh: authTokens.refresh
})
console.log('refreshed1')
const data = authTokens
data.refresh = tokens.refresh
data.access = tokens.access
console.log('refreshed2')
localStorage.setItem('authTokens',JSON.stringify(data))
req.headers.Authorization = `Bearer ${tokens.access}`
console.log('refreshed3')
return req
}, error => {
return Promise.reject(error);
});
export default instance;
In my actions I import the instance as in this example
in actions.js
import axios from '../axiosHelper.js '
...(here my imports and actions)...
const action = async () =>{
const config = { headers: {
'Content-type': 'application/json',
Authorization: `Bearer ${token.access}` // token from the redux store
}
const { data } = await axios.get(
`/api/my-url/`,
config
)
...(send data to reducer)...
}
then in my components, I call my actions via dispatch in useEffect.
in Home.js
const Home = (props) => {
const dispatch = useDispatch()
useEffect(() =>{
dispatch(action())
}, [dispatch])
return (<my component/>)
}
I believe the problem has something to do with dispatch. If the access token is valid, then everything works fine. However, if it is expired, my interceptor will send the request to refresh the tokens (which is successful), but as soon as the tokens are returned, my component tries to render and the rest of the code in the axios interceptor is ignored (I checked this with console log ...), and the original request is not sent (I checked that it does not arrive at my backend server).
I would really appreciate some help as I cannot figure out hoe to solve the problem!
I solved the issue by importing the redux store into my axiosHelper.js script, and then using store.dispatch(refreshaction(authTokens.refresh)) inside my axios interceptor.
I just added a very basic caching system for my app on GET requests via axios-cache-adapter library.
Works great, but there are situations where I would like to get fresh data (especially after updating some value). How can I accomplish this while using this library?
The docs make no mention of cache busting.
Below is my setup.
import Axios from 'axios'
import store from '#/store'
import { setupCache } from 'axios-cache-adapter'
const cache = setupCache({
maxAge: 15 * 60 * 1000
})
const AxiosConfig = Axios.create({
baseURL: process.env.VUE_APP_BASE_API_URL,
adapter: cache.adapter
})
const addToken = (config) => {
const token = store.getters['AuthModule/getAuthToken']
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
}
AxiosConfig.interceptors.request.use(
(config) => {
addToken(config)
return config
},
(error) => {
return Promise.reject(error)
}
)
export default AxiosConfig
You can invalidate the cache entry per request by setting up the cache.invalidate hook to remove the entry based on a flag (e.g., named "clearCacheEntry "):
import { setup } from 'axios-cache-adapter'
const axiosInstance = setup({
cache: {
// Invalidate only when a specific option is passed through config
invalidate: async (config, request) => {
if (request.clearCacheEntry) {
await config.store.removeItem(config.uuid)
}
}
}
})
Then set the clearCacheEntry option when making the request:
axiosInstance.get('https://httpbin.org/get', { clearCacheEntry: true })
.then(response => {
// Response should not come from cache
assert.ok(response.request.fromCache !== true)
})