useContext inside axios interceptor - reactjs

I cant figure out why my useContext is not being called in this function:
import { useContext } from "react";
import { MyContext } from "../contexts/MyContext.js";
import axios from "axios";
const baseURL = "...";
const axiosInstance = axios.create({
baseURL: baseURL,
timeout: 5000,
.
.
.
});
axiosInstance.interceptors.response.use(
(response) => response,
async (error) => {
const { setUser } = useContext(MyContext);
console.log("anything after this line is not running!!!!");
setUser(null)
.
.
.
My goal is to use an interceptor to check if the token is live and if its not clear the user and do the login. I'm using the same context in my other react components. And its working fine there, its just not running here! any idea whats I'm doing wrong?

I had the same issue as you. Here is how I solved it:
You can only use useContext inside a functional component which is why you can't execute setUser inside your axios interceptors.
What you can do though is to create a separate file called WithAxios:
// WithAxios.js
import { useContext, useEffect } from 'react'
import axios from 'axios'
const WithAxios = ({ children }) => {
const { setUser } = useContext(MyContext);
useEffect(() => {
axios.interceptors.response.use(response => response, async (error) => {
setUser(null)
})
}, [setUser])
return children
}
export default WithAxios
And then add WithAxios after MyContext.Provider to get access to your context like this for example:
// App.js
const App = () => {
const [user, setUser] = useState(initialState)
return (
<MyContext.Provider value={{ setUser }}>
<WithAxios>
{/* render the rest of your components here */}
</WithAxios>
</MyContext.Provider>
)
}

I don't have any issues catching the errors in this schema. are you catching them in the axios interceptor? here how I modified it:
useMemo(() => {
axiosInstance.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
// Prevent infinite loops
if (
error.response.status === 401 &&
originalRequest.url === // your auth url ***
) {
handleLogout();
return Promise.reject(error);
}
if (
error.response.status === 401 &&
error.response.data.detail === "Token is invalid or expired"
) {
handleLogout(); // a function to handle logout (house keeping ... )
return Promise.reject(error);
}
if (
error.response.data.code === "token_not_valid" &&
error.response.status === 401 &&
error.response.statusText === "Unauthorized"
) {
const refreshToken = // get the refresh token from where you store
if (refreshToken && refreshToken !== "undefined") {
const tokenParts = JSON.parse(atob(refreshToken.split(".")[1]));
// exp date in token is expressed in seconds, while now() returns milliseconds:
const now = Math.ceil(Date.now() / 1000);
if (tokenParts.exp > now) {
try {
const response = await axiosInstance.post(
"***your auth url****",
{
//your refresh parameters
refresh: refreshToken,
}
);
// some internal stuff here ***
return axiosInstance(originalRequest);
} catch (err) {
console.log(err);
handleLogout();
}
} else {
console.log("Refresh token is expired", tokenParts.exp, now);
handleLogout();
}
} else {
console.log("Refresh token not available.");
handleLogout();
}
}
// specific error handling done elsewhere
return Promise.reject(error);
}
);
}, [setUser]);

Related

React refresh token interceptor

I have a problem with refreshing a token. After I updated the token, a request should be made with my configurations, but this does not happen. The token is updated, but the second time the getIserInfo method is executed, it does not work.
My axios interceptors:
import axios from "axios";
import {getToken, logOut, refreshToken, setUser} from "#/services/auth.service";
const HTTP = axios.create({baseURL: process.env.REACT_APP_API_ENDPOINT});
HTTP.interceptors.request.use(
config => {
const token = getToken();
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
}
return config;
},
error => Promise.reject(error)
)
HTTP.interceptors.response.use(
response => response,
async error => {
debugger;
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const response = await refreshToken();
const {data} = await response;
setUser(data);
} catch (e) {
logOut();
}
return HTTP(originalRequest);
}
return Promise.reject(error);
}
)
export default HTTP
After executing the token, the getUserInfo method should have worked again
Works after removing headers
delete originalRequest["headers"]
return HTTP(originalRequest);
The problem was in axios 1.0+. After removing and switching to version 0.27, everything worked. It's most likely a bug on their part.

How to logout automatically when session expires while using createAsyncThunk and axios (withcredential) option using react and redux toolkit?

I am trying to logout the user when the session expires after a certain period of time. I am using redux-toolkit with react for my API calls and, hence, using the createAsyncThunk middleware for doing so.
I have around 60 API calls made in maybe 20 slices throughout my application. Also, there is a async function for logout too that is fired up on the button click. Now the problem that I am facing is that if the session expires, I am not able to logout the user automatically. If I had to give him the message, then I had to take up that message from every api call and make sure that every screen of mine has a logic to notify the Unautherised message.
I did check a method called Polling that calls an API after a certain given time. And I believe that this is not a very efficient way to handle this problem.
**Here is a little code that will help you understand how my API calls are being made in the slices of my application. **
// Here is the custom created api that has axios and withcredentials value
import axios from "axios";
const api = axios.create({
baseURL:
process.env.NODE_ENV === "development" ? process.env.REACT_APP_BASEURL : "",
headers: {
"Content-Type": "application/json",
},
withCredentials: true,
});
export default api;
// My Logout Function!!
export const logoutUser = createAsyncThunk(
"userSlice/logoutUser",
async (thunkAPI) => {
try {
const response = await api.get("/api/admin/logout");
if (response.status === 200) {
return response.data;
} else {
return thunkAPI.rejectWithValue(response.data);
}
} catch (e) {
return thunkAPI.rejectWithValue(e.response.data);
}
}
);
I want to dispatch this function whenever there is a response status-code is 401 - Unauthorised. But I don't want to keep redundant code for all my other API calls calling this function. If there is a middleware that might help handle this, that would be great, or any solution will be fine.
// Rest of the APIs are called in this way.
..........
export const getStatus = createAsyncThunk(
"orgStat/getStatus",
async (thunkAPI) => {
try {
const response = await api.get("/api/admin/orgstat");
if (response.status === 200) {
return response.data;
} else {
return thunkAPI.rejectWithValue(response.data);
}
} catch (e) {
return thunkAPI.rejectWithValue(e.response.data);
}
}
);
const OrgStatusSlice = createSlice({
name: "orgStat",
initialState,
reducers: {
.......
},
extraReducers: {
[getStatus.pending]: (state) => {
state.isFetching = true;
},
[getStatus.rejected]: (state, { payload }) => {
state.isFetching = false;
state.isError = true;
state.isMessage = payload.message;
},
[getStatus.fulfilled]: (state, { payload }) => {
state.isFetching = false;
state.data = payload.data;
},
},
});
.......
If needed any more clearence please comment I will edit the post with the same.
Thank You!!
import axios from 'axios'
import errorParser from '../services/errorParser'
import toast from 'react-hot-toast'
import {BaseQueryFn} from '#reduxjs/toolkit/query'
import {baseQueryType} from './apiService/types/types'
import store from './store'
import {handleAuth} from './common/commonSlice'
import storageService from '#services/storageService'
// let controller = new AbortController()
export const axiosBaseQuery =
(
{baseUrl}: {baseUrl: string} = {baseUrl: ''}
): BaseQueryFn<baseQueryType, unknown, unknown> =>
async ({url, method, data, csrf, params}) => {
const API = axios.create({
baseURL: baseUrl,
})
API.interceptors.response.use(
(res) => {
if (
res.data?.responseCode === 1023 ||
res.data?.responseCode === 6023
) {
if(res.data?.responseCode === 1023){
console.log('session expired')
store.dispatch(handleSession(false))
return
}
console.log('Lopgged in somewhere else')
store.dispatch(handleSession(false))
storageService.clearStorage()
// store.dispatch(baseSliceWithTags.util.resetApiState())
return
// }, 1000)
}
return res
},
(error) => {
const expectedError =
error.response?.status >= 400 &&
error.response?.status < 500
if (!expectedError) {
if (error?.message !== 'canceled') {
toast.error('An unexpected error occurrred.')
}
}
if (error.response?.status === 401) {
// Storage.clearJWTToken();
// window.location.assign('/')
}
return Promise.reject(error)
}
)
try {
let headers = {}
if (csrf) headers = {...csrf}
const result = await API({
url: url,
method,
data,
headers,
params: params ? params : '',
baseURL: baseUrl,
// signal: controller.signal,
})
return {data: result.data}
} catch (axiosError) {
const err: any = axiosError
return {
error: {
status: errorParser.parseError(err.response?.status),
data: err.response?.data,
},
}
}
}
I am also using RTK with Axios. You can refer to the attached image.

AUTH in next js with React Query

Tell me I want to make middleware to protect the administrator pages, I have authorization through sessions on the project. The project itself is on next js . I want to use React Query to protect pages, but I get the error: An error has occurred: Unexpected token < in JSON at position 0 react query
APi:
import type { NextApiRequest, NextApiResponse } from 'next'
import { route } from 'next/dist/server/router'
import { useRouter } from 'next/router'
import checkSession from '../../src/services/checkCookie'
export async function middleware(req: NextApiRequest,res:NextApiResponse) {
if (req.method === 'GET') {
try {
const router= useRouter()
const sid = req.cookies['sid']
const admin = await checkSession(sid)
console.log(router.pathname)
// if (router.pathname === '/admin/login' || router.pathname === '/admin/regAdmin' || admin) {
// return res.next()
// }
res.send(admin)
const host = process.env.NODE_ENV === 'production' ? process.env.HOST : 'http://localhost:3000'
// return res.redirect(host + '/admin/login')
return res.send({ redirectUrl: '/admin/login' })
}catch (error) {
console.error(error)
res.status(500).send({ message: "Server error" })
}
}else{
res.status(404).send({ message: "adress error" })
}
}
Service in api (checkSessin) :
export default async function checkSession (token: string) {
// const token = req.cookies['sid']
if (typeof window === 'undefined' && token) {
const unsign = (await import('./signature')).unsign
const sessionToken = unsign(token, process.env.SECRET!)
if (sessionToken && typeof sessionToken === 'string') {
const db = (await import('../../prisma')).default
const session = db.session.findUnique({ where: { sessionToken },
include: { admin: true } })
if (session) {
return { admin: session.admin }
}
}
}
}
page admin :
import { NextPage } from "next"
import AdminLayout from "../../src/component/admin/AdminLayout"
import { SalesAdminComponent } from "../../src/component/admin/SalesAdmin"
import { useQuery } from 'react-query'
const AdminTable: NextPage = () => {
const { isLoading, error, data,isSuccess} = useQuery('sid', () =>
fetch('api/checkSession',{
method:'GET',
headers: {
"Content-Type": "application/json"
}
}).then(res =>res.json())
)
if (isLoading) return 'Loading...'
if (error) return 'An error has occurred: ' + error.message
return (
<>
{isSuccess &&
<AdminLayout title="OPEL Admin">
<SalesAdminComponent />
</AdminLayout>
}
{isLoading && <p>Loading..</p>}
{error && <p>Error occurred!</p>}
</>
)
}
export default AdminTable
Lots of things are going on here. The response is probably an HTML page that cannot be parsed to JSON. Please include logs both from the server and the browser.
Also, why are you using the client router on the server?

how to use an Axios interceptor with Next-Auth

I am converting my CRA app to Nextjs and running into some issues with my Axios interceptor pattern.
It works, but I am forced to create and pass an Axios instance to every api call.
Is there a better way to do this?
Here is what I have now:
Profile.js:
import { useSession } from 'next-auth/react'
function Profile(props) {
const { data: session } = useSession()
const [user, setUser] = useState()
useEffect(()=> {
const proc= async ()=> {
const user = await getUser(session?.user?.userId)
setUser(user)
}
proc()
},[])
return <div> Hello {user.userName}<div>
}
getUser.js:
export default async function getUser(userId) {
const axiosInstance = useAxios()
const url = apiBase + `/user/${userId}`
const { data } = await axiosInstance.get(url)
return data
}
useAxios.js:
import axios from 'axios'
import { useSession } from 'next-auth/react'
const getInstance = (token) => {
const axiosApiInstance = axios.create()
axiosApiInstance.interceptors.request.use(
(config) => {
if (token && !config.url.includes('authenticate')) {
config.headers.common = {
Authorization: `${token}`
}
}
return config
},
(error) => {
Promise.reject(error)
}
)
return axiosApiInstance
}
export default function useAxios() {
const session = useSession()
const token = session?.data?.token?.accessToken
return getInstance(token)
}
In case anyone else has this problem, this was how i solved it (using getSession):
credit to:
https://github.com/nextauthjs/next-auth/discussions/3550#discussioncomment-1993281
import axios from 'axios'
import { getSession } from 'next-auth/react'
const ApiClient = () => {
const instance = axios.create()
instance.interceptors.request.use(async (request) => {
const session = await getSession()
if (session) {
request.headers.common = {
Authorization: `${session.token.accessToken}`
}
}
return request
})
instance.interceptors.response.use(
(response) => {
return response
},
(error) => {
console.log(`error`, error)
}
)
return instance
}
export default ApiClient()
There is actually a neat way on including user extended details to session object
// /api/[...nextauth].ts
...
callbacks: {
session({ session, user, token }) {
// fetch user profile here. you could utilize contents of token and user
const profile = getUser(user.userId)
// once done above, you can now attach profile to session object
session.profile = profile;
return session;
}
},
The you could utilize it as:
const { data: session } = useSession()
// Should display profile details not included in session.user
console.log(session.profile)
I know one way to do this is to use
const session = await getSession()
Is there any other way to go about it without using await getSession() because what this does is that it makes a network request to get your session every time your Axios request runs?

Use redux function after refresh token JWT

I have functions
export function configureInterceptors(store) {
axios.interceptors.response.use(
response => response,
error => {
if (error.response && error.response.data) {
if (error.response.status === 401 && err.config) {
const originalRequest = error.config;
originalRequest._retry = true;
store.dispatch(jwtRefresh(originalRequest))
}
throw error;
}
}
);
}
export const jwtRefresh = (originalRequest) => dispatch => {
dispatch(jwtRefreshBegins());
axios
.post('auth/jwt/refresh/', {
refresh: window.localStorage.getItem('refresh')
})
.then(response => {
window.localStorage.setItem('jwt', response.data.access);
originalRequest.headers.Authorization = `JWT ${response.data.access}`;
return axios(originalRequest)
})
.catch(err => {
window.localStorage.removeItem('token');
window.localStorage.removeItem('jwt');
})
};
But all of my requests are in redux function. How can I make redux function again if refresh token was success? I must use redux function, because it change redux state.

Resources