ReactJs showing global alert dialog from global helper - reactjs

I have global helper for axios like this:
import Axios from 'axios';
const CustomAxios = Axios;
CustomAxios.interceptors.response.use(response => {
return response;
}, error => {
const isLogin = error.response.config.url.match(/login/);
if (+error.response.status === 401 && !isLogin) { // Invalid token
localStorage.removeItem('jwt_token');
window.location.href = '/';
} else if (+error.response.status === 440) { // Expired token, try to refresh first
return CustomAxios
.post(`${process.env.REACT_APP_API_URL}/refresh`, null, {
headers: {
Authorization: `Bearer ${localStorage.getItem('jwt_token')}`
}
})
.then(res => {
localStorage.setItem('jwt_token', res.data);
error.config.headers['Authorization'] = `Bearer ${res.data}`
return CustomAxios.request(error.config);
})
} else if (process.env.NODE_ENV === 'production') {
alert("Oops.. Something went wrong..\nPlease contact the admin");
}
return Promise.reject(error);
})
export default CustomAxios;
It went great until my boss tell me to change the alert message to something better like alert dialog from material-ui. How do I show this 'global alert' from my global helper? Thanks for answering !

Related

Remix.run, Remix-Auth destroy user session if token validation fails

I am using Remix, along with Remix-Auth and using the Twitch API/OAuth, which requires that I check in with their /validate endpoint every hour docs. I had someone recommend that I use a resource route and POST to that if the validation endpoint returned a status of 401, however, I need as I stated before the request needs to be sent every hour I figured maybe I could use something like React-Query to POST to the resource route every hour.
Just pointing out that I use createCookieSessionStorage with Remix Auth to create the session
Problem
I haven't been able to achieve the actual session being destroyed and a user being re-routed to the login page, I have left what actual code I have currently any help or suggestions to actually achieve the session being destroyed and be re-routed to the login page if the validation fails would be greatly appreciated.
// React Query client side, checks if the users token is still valid
const { error, data } = useQuery("TV-Revalidate", () =>
fetch("https://id.twitch.tv/oauth2/validate", {
headers: {
Authorization: `Bearer ${user?.token}`,
},
}).then((res) => res.json())
);
The above React Query returns this
// My attempt at the resource route
// ~routes/auth/destroy.server.ts
import { ActionFunction, redirect } from "#remix-run/node";
import { destroySession, getSession } from "~/services/session.server";
export const action: ActionFunction = async ({request}) => {
const session = await getSession(request.headers.get("cookie"))
return redirect("/login", {
headers: {
"Set-Cookie": await destroySession(session)
}
})
}
// Second attempt at resource route
// ~routes/auth/destroy.server.ts
import { ActionFunction, redirect } from "#remix-run/node";
import { destroySession, getSession } from "~/services/session.server";
export const action: ActionFunction = async ({request}) => {
const session = await getSession(request.headers.get("cookie"))
return destroySession(session)
}
I attempted using an if statement to POST to the resource route or else render the page, however, this definitely won't work as React errors out because functions aren't valid as a child and page is blank.
//index.tsx
export default function Index() {
const { user, bits, vali } = useLoaderData();
console.log("loader", vali);
const { error, data } = useQuery("TV-Revalidate", () =>
fetch("https://id.twitch.tv/oauth2/validate", {
headers: {
Authorization: `Bearer ${user?.token}`,
},
}).then((res) => res.json())
);
if (data?.status === 401)
return async () => {
await fetch("~/services/destroy.server", { method: "POST" });
};
else
return ( ... );}
You could use Remix' useFetcher hook.
https://remix.run/docs/en/v1/api/remix#usefetcher
// Resource route
// routes/api/validate
export const loader: LoaderFunction = async ({ request }) => {
const session = await getSession(request);
try {
const { data } = await fetch("https://id.twitch.tv/oauth2/validate", {
headers: {
Authorization: `Bearer ${session.get("token")}`
}
});
return json({
data
}, {
headers: {
"Set-Cookie": await commitSession(session),
}
});
} catch(error) {
return redirect("/login", {
headers: {
"Set-Cookie": await destroySession(session)
}
});
}
}
And then in your route component something like this:
const fetcher = useFetcher();
useEffect(() => {
if (fetcher.type === 'init') {
fetcher.load('/api/validate');
}
}, [fetcher]);
useEffect(() => {
if(fetcher.data?.someValue {
const timeout = setTimeout(() => fetcher.load('/api/validate'), 1 * 60 * 60 * 1000);
return () => clearTimeout(timeout);
}
},[fetcher.data]);

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?

Infinity loop - Axios Interceptor Refresh token in React app

I am using axios interceptor for refresh token when the access token is expired. Here is my code:
import axios from 'axios'
import jwt_decode from "jwt-decode";
import dayjs from 'dayjs'
const baseURL = 'http://127.0.0.1:8000'
let authTokens = localStorage.getItem('authTokens') ? JSON.parse(localStorage.getItem('authTokens')) : null
const axiosInstance = axios.create({
baseURL,
headers:{Authorization: `Bearer ${authTokens?.access}`}
});
axiosInstance.interceptors.request.use(async req => {
if(!authTokens){
authTokens = localStorage.getItem('authTokens') ? JSON.parse(localStorage.getItem('authTokens')) : null
req.headers.Authorization = `Bearer ${authTokens?.access}`
}
const user = jwt_decode(authTokens.access)
const isExpired = dayjs.unix(user.exp).diff(dayjs()) < 1;
if(!isExpired) return req
const response = await axios.post(`${baseURL}/api/token/refresh/`, {
refresh: authTokens.refresh
});
localStorage.setItem('authTokens', JSON.stringify(response.data))
req.headers.Authorization = `Bearer ${response.data.access}`
return req
})
export default axiosInstance;
But I got a problem when calling multiple requests, it goes to an infinite loop. Anyone can help me to fix it. Thanks in advance!
Just use setInterval(() => { your code here }) instead :D
you may see some tutorials like w3schools, etc they always add 0 in the end like : setInterval(() => { do some code }, 0)
but it’s not needed :D because default is 0 :)
I would make the interceptor as response interceptor and not a request interceptor.
axios.interceptors.response.use((response) => {
return response
},
function (error) {
if (error.response.status === 401) {
...fetch token and retry
}

useContext inside axios interceptor

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]);

Resources