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

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
}

Related

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.

Socket.io - Terminal returning data as "null"

I'm building a forum app using reactjs, nodejs, express and mongoDB, where users can create account, login and then post a post text. I want this post to be displayed instantly To all others users, for that i am using socket.io server.
I would appreciate if you check my whole project here in github: (https://github.com/darkpsinight/forum)
However, whenever a user login or connect to the app, i get this null in socket terminal:
Connected user.✅
null
I tried to investigate this null message, and i discovered that data is returning null.
How to make data returns data of createdPostsocket instead of null ?
Edit:
createdPostsocket is initialized as null, and my catch statement returns null, so either i am not assigning a value to createdPostsocket or i am throwing an error in my promise.
Meanwhile, i already assigned a value in Slice at fulfilled case :
[createPost.fulfilled]: (state, action) => {
console.log(action.payload);
window.location.reload(false);
state.createdPostsocket= action.payload.data.data
}
Which means createdPostsocket assigned the value of payload.data.data, right?
My codes:
- Back:
app.js (socket server)
const io = require("socket.io")(4000, {
cors: {
origin: "http://localhost:3000",
},
});
io.on('connection', (socket) => {
console.log('Connected user.✅');
socket.on('addPost', data => {
console.log(data); //returning null in terminal
io.emit('newPost', data)
})
socket.on('disconnect', function (socket) {
console.log('Disconnected user.⛔️');
});
});
- Front:
postsSlice.js:
const initialState = {
posts: [],
createdPostsocket: null
};
const postsSlice = createSlice({
name: "posts",
initialState,
reducers: {},
extraReducers: {
//Create http request 3 cases
[createPost.pending]: (state, action) => {
},
[createPost.fulfilled]: (state, action) => {
console.log(action.payload);
window.location.reload(false);
state.createdPostsocket= action.payload.data.data
},
[createPost.rejected]: (state, action) => {
},
},
});
export const { } = postsSlice.actions;
export const selectPosts = (state) => state.posts
export default postsSlice.reducer;
index.jsx:
const posts = useSelector(selectPosts)
const socket = useRef()
useEffect(() => {
socket.current = io("ws://localhost:4000")
}, [])
useEffect(() => {
socket.current.emit("addPost", posts.createdPostsocket);
console.log (posts.createdPostsocket) //returning null in console developper
}, [posts.createdPostsocket])
postsAPI.js
import { axiosInstance } from "../../config/axios";
import { requests } from "../../config/requests";
//post service
export const PostsService = {
// Create post request
create: (data) => {
console.log(data);
return axiosInstance
.post(requests.postapi.create, data, { credentials: "include" })
.then((res) => {
return res;
})
.catch((err) => {
return err;
});
},
};
- back:
controller.js:
module.exports = {
create: (req, res) => {
const data = {
user: req.user.sub,
text: req.body.text,
/* image: req.file && req.file.filename */
}
console.log(data) //returning user and text
const t = new post(data)
t.save().then(t => t
.populate('user')
.then(post => {
res.status(200).json({
message: 'post created',
data: post
})
})
.catch(err => {
res.status(500).json({
message: 'post not created',
data: null
})
})
)
},
}
Remove window.location.reload(false); so you wont lose your data. window.location.reload(false); will reload your page and delete all the data. However it is not recommanded to use vanilla JavaScript to call the reload method to tell the browser to reload the current page and you will loose any data.

It seems to ignore the Async / Await

On a React page, I have a method called goOut. This method calls upon a Redux action > Node controller > Redux reducer. I can confirm that the correct data values are returned inside the Redux action, the controller method, and the reducer. However, nonetheless, at point 1 below inside the goOut method, it returns undefined.
What am I doing wrong / how could it return undefined if the the reducer is returning the correct values? It is as if the await inside the goOut method is not working...
React page:
import { go_payment } from "../../appRedux/actions/paymentAction";
<button onClick={this.goOut}>
Button
</button>
async goOut(ev) {
try {
const data = { user: parseInt(this.state.userId, 10) };
let result = await this.props.go_payment({data});
console.log(result);
// 1. RETURNS UNDEFINED. As if it tries to execute this line before it has finished the previous line.
{
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators(
{go_payment}, dispatch
);
};
Redux Action:
export const go_payment = (data) => {
let token = getAuthToken();
return (dispatch) => {
axios
.post(`${url}/goController`, data, { headers: { Authorization: `${token}` } })
.then((res) => {
if (res.status === 200) {
// console.log confirms correct data for res.data
return dispatch({ type: GO_SUCCESS, payload: res.data });
})
}
}
Node controller method:
Returns the correct data in json format.
Reducer:
export default function paymentReducer(state = initial_state, action) {
switch (action.type) {
case GO_SUCCESS:
// console.log confirms action.payload contains the correct data
return { ...state, goData: action.payload, couponData: "" };
}
}

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

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... :(

Axios-Redux in React to an Express endpoint-both .then and .catch triggered

I'm using a Redux Form to send a POST call to an Express endpoint. The endpoint is supposed to return the successfully saved object, or an error.
The endpoint successfully saves the posted data and returns the JSON. But Axios in the Redux action picks up both the .then and the .catch triggers-in the following action, it logs the following:
successful response: { …}
failure response: undefined
What am I doing wrong?
My Axios action:
export function addPlot(props) {
const user = JSON.parse(localStorage.getItem('user'));
return function(dispatch) {
axios
.post(
`${ROOT_URL}/plots`,
{
props
},
{ headers: { authorization: user.token } }
)
.then(response => {
console.log('successful response: ', response.data);
const plotModal = document.getElementById('plotModal');
plotModal.modal('dispose');
dispatch({ type: PLOT_ADDED, payload: response.data });
dispatch({ type: ADDING_PLOT, payload: false });
dispatch({
type: NEW_PLOT_GEOJSON,
payload: ''
});
})
.catch(response => {
console.log('failure response: ', response.data);
dispatch(authError(PLOT_ADD_FAILURE, 'Failed to add plot'));
});
}
My endpoint:
exports.newPlot = async (req, res, next) => {
console.log(JSON.stringify(req.body.props));
let company;
if (req.user.companyCode !== 'Trellis') {
company = req.user.companyCode;
} else {
company = req.body.props.company;
}
const {
name,
feature,
growerPhone,
plotCode,
rootStock,
region,
variety,
grower,
planted
} = req.body.props;
const plot = new Plot({
name,
grower,
variety,
planted,
region,
rootStock,
plotCode,
growerPhone,
feature,
company
});
try {
const newPlot = await plot.save();
res.json(newPlot);
} catch (e) {
console.log("couldn't save new plot", JSON.stringify(e));
return res.status(422).send({ error: { message: e, resend: true } });
}
};
You could use redux-thunk middleware to manage async actions.
The problem I see is that you are not dispatching the axios action, you must call dispatch(this.props.addPlot(props))in order to do something in the redux store.

Resources