is it possible to use if statement in calling REST api? - reactjs

OS : Window 10
IDE TOOLS : VSC
node : v12.14.1
Hi i'm not good at English. so my expressions will be little bit awkward.
I'm using Spring boot REST API and client-side is react.js
I'm trying to use refresh Token, Access Token with jwt.
What i want to do is,
Before calling rest api, If accessToken is invalid with timeout in client side,
get Requesttoken in localStorage and send it to serverside and reinssuance accessToken and refreshToken.
And store it again. Then i call rest api what i want to call it first.
Here is my question.
Is it possible that Rest api has if statement ?
api.js
const getAccessToken = () => {
const accessToken = sessionStorage.getItem('accessToken');
if (!accessToken) {
window.location.href = "http://localhost:3000";
return alert('Login first');
} else if (accessToken && !validateToken()) {
// ~~~~ Here is what i want to ask~~~~
is it possible in react.js???
const refreshToken = localStorage.getItem("refreshToken");
getAtWithRefreshToken(refreshToken);
sessionStorage.setItem('')
return accessToken;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
} else if (accessToken && validateToken()) {
console.log("token is Valid~~~");
return accessToken;
}}
export const getBoardList = (searchType = '', searchString = '', page) =>
axios.get("http://localhost:8080/jpa/board/",
{
params: {
searchType,
searchString,
page
},
headers: {
'Authorization': getAccessToken()
}
}
)
This is my first Question in StackOverFlow... Please Let me know in the comments if there is anything I need to explain.
Sorry you for you that spend many time in my promiscuous question.
Hope you guys always in healthy.
+ api.js
getAtwithRefreshToken
export const getAtWithRefreshToken = (refreshToken) =>
axios.post("http://localhost:8080/jpa/system/getat",
{
refreshToken
}
)
and in module,
export default handleActions({
..(another pender)....
...pender({
type: GET_AT, // getAtWithRefreshToken
onPending: (state, action) => {
return state; // do something
},
onSuccess: (state, action) => {
const result = action.payload.data.data;
sessionStorage.setItem('role', result.role);// role : [ROLE_ADMIN]
sessionStorage.setItem('accessToken', result.accessToken);
sessionStorage.setItem('memberId', result.memberId); // id : admin
localStorage.setItem('refreshToken', result.refreshToken);
return state
},
onFailure: (state, action) => {
alert(action);
console.log(action);
return state; // do something
}
}),
..(another pender)....
, initialState);
and in container, i uses terrible thing like....
getBoardList = async (searchType, searchString, page, size, direction) => {
this.getAccessToken();
const { boardActions } = this.props;
try {
this.getAccessToken();
await boardActions.getBoardList(searchType, searchString, page, size);
} catch (e) {
console.log("error log :" + e);
}
this.getBoardCount(searchType, searchString);
}
and my page shows
Unhandled Rejection (InvalidTokenError):
Invalid token specified: Cannot read property 'replace' of undefined
such a mess. my brain stopped... :(

Related

401 Unauthorized react axios

I am trying to make an ecommerce using react, redux toolkit and axios
the problem is that I want the user to log in and get his cart from the backend right after the login
it always fails the and says (unauthorized) when i first login because it can't find the token
then after refresh it says unauthorized one more time
after the third refresh it works
this is my get cart
export const getCart = createAsyncThunk("cart/getcart", async () => {
const response = await axios.get("http://127.0.0.1:8000/techcart/get_cart/", {
headers: {
Authorization: `Token ${token}`,
},
});
return response.data;
});
const cartSlice = createSlice({
name: "cart",
initialState: {
cart: [],
cartItemsIds :[],
},
builder.addCase(getCart.fulfilled, (state, action) => {
state.cart = action.payload;
and this is my login function
export const login = createAsyncThunk(
"auth/login",
async ({ email, password }, thunkAPI) => {
try {
const response = await axios.post(
"http://127.0.0.1:8000/techcart/login/",
{ username: email, password }
);
localStorage.setItem("user", JSON.stringify(response.data));
return response.data;
} catch (error) {}
}
);
const initialState = user
? { isLoggedIn: true, user }
: { isLoggedIn: false, user: null };
builder.addCase(login.fulfilled, (state, action) => {
state.isLoggedIn = true;
state.user = action.payload;
here is where i am doing the login
const HandleLogin = () => {
dispatch(login({ email, password }));
};
useEffect(()=> {
if(isLoggedIn){
navigate('/')
dispatch(getCart())
}
},[isLoggedIn])
Cart page
useEffect(() => {
dispatch(getCart());
}, []);
here is where im defining my token :
export let user = JSON.parse(localStorage.getItem("user")) ? JSON.parse(localStorage.getItem("user")) : null;
export let userId = user ? user.user_id : null;
export let token = user!=null ? user.token : null;
and here is where im importing it in my cart slice
import { user, token } from "../../constants";
im using redux persist to persist the state of my cart
if anyone can help me i'm so thankful
here is what happens
You're initializing your token directly when your js is executed. So when you retrieve it, it is undefined.
Ans when you do the login, you're indeed storing your token, but you're not updating it in your application.
I can see you're using redux, so store your token in your redux store, and before sending your api call to retrieve your cart, retrieve your token from redux, to always have the latest value of your token

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.

React Native + Redux + Saga while refresh token is refreshing other API calls are made in parallel

Hey there fellow developers,
I have an issue in my React Native + Redux + Saga + Axios app, where the refresh token is refreshed after 15 minutes. If the app is in an active state, i.e. the user is using it, there are no problems, and token is refreshed properly. However, when the app is inactive or in the background, and user opens it after 15 or more minutes of using it, app sends a POST request to renew the token, but while that is done, other API calls such as fetching user profile and some other features present on the initial screen (Home screen) are made in parallel, resulting in errors as the token renewal has not been finished yet.
In the Axios interceptors I have the following:
interface DispatchActions {
success?: (payload: any) => AnyAction
failure: (payload: any) => AnyAction
}
interface Options {
withoutAuth?: boolean
withoutNotification?: boolean
withoutInterceptors?: boolean
}
export const useApi = (
type: APITypes,
actions?: DispatchActions,
options?: Options
): AxiosInstance => {
const mockApiReqInterceptor = async (config: AxiosRequestConfig) => {
const {
authState: { data: authData, tokenRefreshing },
} = store.getState()
if (tokenRefreshing?.fetching) {
setTimeout(() => mockApiReqInterceptor(config), 1000)
} else if (!options?.withoutAuth && authData?.accessToken && authData?.userInfo?.exp) {
if (authData?.userInfo.exp * 1000 - Date.now() <= 10000) {
store.dispatch(setRefreshingToken(true))
const { data } = await axios.post(`${AUTH_SERVICE_URL}token/renew`, {
refreshToken: authData?.refreshToken,
})
if (data?.accessToken) {
store.dispatch(setAccessToken(data.accessToken))
config.headers.Authorization = `Bearer ${data.accessToken}`
}
store.dispatch(setRefreshingToken(false))
} else {
config.headers.Authorization = `Bearer ${authData?.accessToken}`
}
}
return config
}
Moreover, lately, I've tried to implement an automatic log out after 15 minutes, however, when I dispatch the logout action, the app returns back to the login screen, however token refreshing triggers and the app tries to log in automatically, switching the screen to Home screen. In the router I am checking for the access token and based on whether the user has one, the navigator directs the user to the Home or Login screen. Something along these lines:
<NavigationContainer fallback={<Text>Loading...</Text>} ref={navigationRef}>
{authState?.accessToken ? (
<AuthorizedRoutes Stack={Stack} />
) : (
<AuthRouter....
</NavigationContainer>
Moreover, this is my logic for checking the inactivity:
const appState = useRef(AppState.currentState)
const [appStateVisible, setAppStateVisible] = useState(appState.current)
const {
profileState: { user, loading },
authState: { data: authData, rcData, tokenRefreshing },
} = useSelector((state: RootState) => state)
useEffect(() => {
if (authData?.userInfo.sub && !tokenRefreshing?.fetching) {
dispatch(getOrCreateProfileRequest(authData.userInfo.sub, locale.toUpperCase()))
}
}, [authData?.userInfo.sub, tokenRefreshing?.fetching])
useEffect(() => {
checkInactivity()
}, [appStateVisible])
const checkInactivity = async () => {
const prevInactivityTime = await AsyncStorage.getItem('inactivityTime')
if (
prevInactivityTime &&
differenceInMinutes(new Date(), new Date(JSON.parse(prevInactivityTime))) > 15
) {
AsyncStorage.setItem('inactivityTime', JSON.stringify(new Date()))
dispatch(signout())
}
const subscription = AppState.addEventListener('change', nextAppState => {
if (appState.current === 'active' && nextAppState.match(/inactive|background/)) {
AsyncStorage.setItem('inactivityTime', JSON.stringify(new Date()))
}
appState.current = nextAppState
setAppStateVisible(appState.current)
})
return () => {
subscription.remove()
}
}
Could anyone provide me with some assistance on this matter and explain what goes wrong here?

How to Create Middleware for refresh token in Reactjs with axios and redux

i am working with reactjs on front end the issue is after certain time period the accessToken is expired and server send status of 401(unauthorized) then i need to send refresh token back to server it works fine until i manually send the refresh token i set the setInterval function but thats not a good approach how to automatically send it when token is expired.
i also google it but everyone is talking about creating middleware anyone please give me the hint how to create that middleware or any other solution or link any article related to it . i created this but this didnt works for me however when server send status of 401 then middleware ran but it dosent dispatch my refreshToken() function
const customMiddleWare = store => next => action => {
axios.interceptors.response.use(function (response) {
return response;
}, function (error) {
if(error.status === 401) {
// do something when unauthorized
store.dispatch(refreshToken());
}
return Promise.reject(error);
});
console.log("Middleware triggered:", action);
next(action);
}
By the way i am using redux, redux-thunk and axios. thanks,
some time ago i used to use the next way:
First of all i created some api folder, where each function returns data for axios requests
// /api.js
export function signIn (data) {
return {
method: 'post',
api: '/sign-in'
data: data
}
}
export function signUp (data) {
return {
method: 'post',
api: '/registration'
data: data
}
}
then i generated action type by specific rule, like: SIN_IN_REQUEST, where: SIGN_IN means signIn function in /api.js; REQUEST means that you need to do api request. As result my middleware looked like the next:
// request middleware
const instance = axios.create({
baseURL: '/api'
});
function camelize(str) {
return str.replace(/(?:^\w|[A-Z]|\b\w)/g, function(word, index) {
return index === 0 ? word.toLowerCase() : word.toUpperCase();
}).replace(/\s+/g, '');
}
const customMiddleWare = store => next => action => {
if (!action.type.endsWith('_REQUEST')) {
next();
return;
}
const methodName = action.type.replace('_REQUEST', ''); // removed _REQUEST from action type
const camelCaseMethodName = camelize(methodName); // the result is "signIn"
const method = api[camelCaseMethodName];
if (!method) {
next();
return;
}
const dataForRequest = method(action.payload);
try {
const response = await instance(dataForRequest);
const newActionType = action.type.replace('_REQUEST', '_SUCCESS');
dispatch({
type: newActionType,
payload: {
requestPayload: action.payload,
response: response,
}
})
} catch(error) {
if (error.status === '401') {
dispatch(refreshToken());
next();
return;
}
const newActionType = action.type.replace('_REQUEST', '_FAILURE');
dispatch({
type: newActionType,
payload: {
requestPayload: action.payload,
error: error,
}
})
}
next();
}
After that you can easily manage any api request in your application like that:
function someTHunkMethod(username, password) {
return (dispatch, getState) => {
dispatch({
type: 'SIGN_IN_REQUEST',
payload: {
username,
password
}
})
}
}
function oneMoreThunk(data) {
return (dispatch, getState) => {
dispatch({
type: 'GET_USERS_REQUEST',
payload: data
})
}
}
And in reducer do something like that
...
switch (action.type) {
case 'SIGN_REQUEST':
return {
isLoading: true,
user: null
}
case 'SIGN_SUCCESS':
return {
isLoading: false,
user: action.payload.response.data
}
case 'SIGN_FAILURE':
return {
isLoading: false,
user: null
}
default:
return state
}

How to refresh JWT token automagically in React app with NodeJS?

Last few days I tried to write some middleware that checks wether the token stored in the redux-store is still valid and not reached it's expiry date. If it is not valid anymore it should refresh the token before executing any other async call. The problem I am encountering right now is that the async redux functions in the components are called first before the middleware is being called.
Currently I wrote the following middleware:
reduxMiddleware.js
const refreshJwt = ({ dispatch, getState }) => {
return (next) => (action) => {
console.log(typeof action);
if (typeof action === "function") {
if (getState().authentication.token) {
// decode jwt so that we know if and when it expires
var tokenExpiration = parseJwt(getState().authentication.token).exp;
if (
tokenExpiration &&
moment(tokenExpiration) <
moment(Math.floor(Date.now().valueOf() / 1000))._i
) {
console.log("start refreshing");
startRefreshToken(getState().authentication.refreshToken).then(
(token) => {
console.log("done refreshing");
dispatch(updateAccessToken(token));
next(action);
}
);
}
}
}
return next(action);
};
};
export default refreshJwt;
I apply this middleware like so:
export default () => {
const store = createStore(
combineReducers({
authentication: authenticationReducer,
venue: venueReducer,
tables: tableReducer
}),
composeEnhancers(applyMiddleware(refreshJwt, thunk))
);
return store;
};
The startRefreshToken code is:
const startRefreshToken = (refresh_token) => {
return httpPost(
process.env.NODE_ENV
? `https://tabbs-api.herokuapp.com/api/v1/token`
: `http://localhost:3000/api/v1/token`,
{
refresh_token
}
)
.then((response) => {
localStorage.setItem(
"authentication",
JSON.stringify({
token: response.data.token,
refreshToken: refresh_token
})
);
return response.data.token;
})
.catch((error) => {
return Promise.reject(error.response);
});
};
Order of calling:
Legend:
Executing call now stands for the function being called in the component
start refreshing stands for the middleware being called
Currently I am experiencing the following issue:
When a async function in the components didComponentMount is being called, it will be called before the middleware function is being called. This is causing that it will be using the old token stored in the redux/local storage.
I really can't find the issue till today and would like to get some external help for this issue.
I am aware that this is duplicate of :
How to use Redux to refresh JWT token?
Thanks for the help. If you'll need additional context / code please do not hesitate to comment. I'll add it to codesandbox.
Best Kevin.

Resources