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.
Related
I'm trying to understand the state in React, having a background with Vue.js.
I'm building a login which fetches a JWT-token, then stores the token in a global store so we can use it for subsequent API-calls. I'm using an axios interceptor to resolve the token from the store. However, the token is always an old version/from previous render.
I've read about the React lifecycle but this function is not used in the rendering. It's used as a callback. I understand that setting the state is async. I've tried wrapping the interceptor in a useEffect(.., [tokenStore.token]) and using setTimeout(). I feel like I'm missing something.
Why is my state not being updated in my callbacks? Am I going about this in an non-idiomatic way?
Usage:
<button onPress={() => loginWithToken('abc')}>
Sign In
</button>
User hook:
export function useUserState() {
const api = useApi();
function loginWithToken(token) {
tokenState.setToken(token);
api
.request('get', 'currentUser')
.then((data) => {
console.log(data);
})
.catch((errors) => {
console.log(errors);
});
}
}
The api:
export default function useApi(hasFiles = false) {
const tokenState = useTokenState();
const client = axios.create(/*...*/);
client.interceptors.request.use(function (config) {
config.headers!.Authorization = tokenState.token
? `Bearer ${tokenState.token}`
: '';
return config;
});
// ...
}
Token store using hookstate:
const tokenState = createState({
token: null,
});
export function useTokenState() {
const state = useState(tokenState);
return {
token: state.token.get(),
setToken: (token: string | null) => {
console.log('setToken: ' + token);
state.set({ token });
},
};
}
I solved it by updating the defaults instead:
setToken: token => {
apiClient.defaults.headers.Authorization = token
? `Bearer ${token}`
: undefined;
// ...
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... :(
I am trying to dispatch an action and using middleware to check if token expired and if expired I am generating new token. I am then using this new token in api. The problem is the code is flowing asynchronously. The api is using old token instead of the generated new token. Please help me resolve the issue.
Note - middleware code is in index.js file
this.props.checkToken(oldToken) //action dispatched goes to middleware
apicall(window.sessionSTorage.getItem('token')) //api using token
const mapDispatchToProps = (dispatch) => {
return {
checkToken: (token) => dispatch (actionCreators.checkToken(token))
}
}
export default connect(null, mapDispatchToProps)(myComponent)
Action Creator -
export const checkToken = (token) => {
return {
type: actionTypes.checkToken,
token: token,
}}
MIddleware -
//Middleware code here
const logger = store => {
return next => {
return action => {
if (tokenExpired) {
fetch (url, {
method: 'POST',
body: JSON.stringify(refresh),
headers: {
'Content-Type': 'application/json',
}
})
.then ((response) => response.json())
.then ((findresponse) => {
window.sessionStorage.setItem('token', findresponse.token);
})
}
return next(action);
}
}}
Action will return promise so you can do apicall on successful completion of the promise
this.props.checkToken(oldToken).then(()=>apicall(window.sessionSTorage.getItem('token'))
or using async/await
await this.props.checkToken(oldToken) //action dispatched goes to middleware
apicall(window.sessionSTorage.getItem('token')) //api using token
I think that there are many solutions for your question(edit the middleware or using RXJS subscription or create another middle-ware to handle token... ) but I don't think this is a good practice. This is because we don't need to dispatch an action to checktoken.
It should be done by the request middleware which runs before every requests or when the location path be changed. Your solution also leads to the difficulties to refresh the token and re-call the existing requests with the new one.
This is stemming off of this SO Question
I am trying to integrate redux-thunk with my API middleware. The current flow of logic is like this:
action dispatched from component this.props.onVerifyLogin();
=>
action goes to action creator which creates an API call to the middleware like so:
// imports
const verifyLoginAC = createAction(API, apiPayloadCreator);
export const verifyLogin = () => dispatch => {
return verifyLoginAC({
url: "/verify/",
method: "POST",
data: {
token: `${
localStorage.getItem("token")
? localStorage.getItem("token")
: "not_valid_token"
}`
},
onSuccess: result => verifiedLogin(dispatch, result),
onFailure: result => dispatch(failedLogin(result))
});
};
const verifiedLogin = (dispatch, data) => {
console.log("verifiedLogin");
const user = {
...data.user
};
dispatch(setUser(user));
dispatch({
type: IS_LOGGED_IN,
payload: true
});
};
// failedLogin function
const setUser = createAction(SET_USER);
apiPayloadCreator in utils/appUtils:
const noOp = () => ({ type: "NO_OP" });
export const apiPayloadCreator = ({
url = "/",
method = "GET",
onSuccess = noOp,
onFailure = noOp,
label = "",
isAuthenticated = false,
data = null
}) => {
return {
url,
method,
onSuccess,
onFailure,
isAuthenticated,
data,
label
};
};
and then the middleware intercepts and performs the actual API call:
// imports
// axios default config
const api = ({ dispatch }) => next => action => {
next(action);
console.log("IN API");
console.log("Action: ", action);
// this is where I suspect it is failing. It expects an action object
// but is receiving a function (see below for console.log output)
if (action.type !== API) return;
// handle Axios, fire onSuccess/onFailure, etc
The action is created but is a function instead of an action creator (I understand this is intended for redux-thunk). But when my API goes to check action.type it is not API so it returns, never actually doing anything including call the onSuccess function. I have tried to also add redux-thunk before api in the applyMiddleware but then none of my API actions fire. Can someone assist?
Edit:
This is the received data to the API middleware:
ƒ (dispatch) {
return verifyLoginAC({
url: "/verify/",
method: "POST",
data: {
token: "" + (localStorage.getItem("token") ? localStorage.getItem("token") : "not_valid_toke…
Status Update:
Still unable to get it work properly. It seems like redux-saga has a pretty good following also, should I try that instead?
My API was interferring. I switched to redux-saga and got everything working like so:
/**
* Redux-saga generator that watches for an action of type
* VERIFY_LOGIN, and then runs the verifyLogin generator
*/
export function* watchVerifyLogin() {
yield takeEvery(VERIFY_LOGIN, verifyLogin);
}
/**
* Redux-saga generator that is called by watchVerifyLogin and queries the
* api to verify that the current token in localStorage is still valid.
* IF SO: SET loggedIn = true, and user = response.data.user
* IF NOT: SET loggedIn = false, and user = {} (blank object}
*/
export function* verifyLogin() {
try {
apiStart(VERIFY_LOGIN);
const token = yield select(selectToken);
const response = yield call(axios.post, "/verify/", {
// use redux-saga's select method to select the token from the state
token: token
});
yield put(setUser(response.data.user));
yield put(setLoggedIn(true));
apiEnd(VERIFY_LOGIN);
} catch (error) {
apiEnd(VERIFY_LOGIN);
yield put(setLoggedIn(false));
yield put(setUser({})); // SET USER TO BLANK OBJECT
}
}
I am building an react / redux webapp where I am using a service to make all my API calls. Whenever the API returns 401 - Unauthorized I want to dispatch a logout action to my redux store.
The problem is now that my api-service is no react component, so I cannot get a reference to dispatch or actions.
What I did first was exporting the store and calling dispatch manually, but as I read here How to dispatch a Redux action with a timeout? that seems to be a bad practice because it requires the store to be a singleton, which makes testing hard and rendering on the server impossible because we need different stores for each user.
I am already using react-thunk (https://github.com/gaearon/redux-thunk) but I dont see how I can injectdispatch` into non-react components.
What do I need to do? Or is it generally a bad practice to dispatch actions outside from react components?
This is what my api.services.ts looks like right now:
... other imports
// !!!!!-> I want to get rid of this import
import {store} from '../';
export const fetchWithAuth = (url: string, method: TMethod = 'GET', data: any = null): Promise<TResponseData> => {
let promise = new Promise((resolve, reject) => {
const headers = {
"Content-Type": "application/json",
"Authorization": getFromStorage('auth_token')
};
const options = {
body: data ? JSON.stringify(data) : null,
method,
headers
};
fetch(url, options).then((response) => {
const statusAsString = response.status.toString();
if (statusAsString.substr(0, 1) !== '2') {
if (statusAsString === '401') {
// !!!!!-> here I need to dispatch the logout action
store.dispatch(UserActions.logout());
}
reject();
} else {
saveToStorage('auth_token', response.headers.get('X-TOKEN'));
resolve({
data: response.body,
headers: response.headers
});
}
})
});
return promise;
};
Thanks!
If you are using redux-thunk, you can return a function from an action creator, which has dispatch has argument:
const doSomeStuff = dispatch => {
fetch(…)
.then(res => res.json())
.then(json => dispatch({
type: 'dostuffsuccess',
payload: { json }
}))
.catch(err => dispatch({
type: 'dostufferr',
payload: { err }
}))
}
Another option is to use middleware for remote stuff. This works the way, that middle can test the type of an action and then transform it into on or multiple others. have a look here, it is similar, even if is basically about animations, the answer ends with some explanation about how to use middleware for remote requests.
maybe you can try to use middleware to catch the error and dispatch the logout action,
but in that case, the problem is you have to dispatch error in action creator which need to check the log status
api: throw the error
if (statusAsString === '401') {
// !!!!!-> here I need to dispatch the logout action
throw new Error('401')
}
action creator: catch error from api, and dispatch error action
fetchSometing(ur)
.then(...)
.catch(err => dispatch({
type: fetchSometingError,
err: err
})
middleware: catch the error with 401 message, and dispatch logout action
const authMiddleware = (store) => (next) => (action) => {
if (action.error.message === '401') {
store.dispatch(UserActions.logout())
}
}
You should have your api call be completely independent from redux. It should return a promise (like it currently does), resolve in the happy case and reject with a parameter that tells the status. Something like
if (statusAsString === '401') {
reject({ logout: true })
}
reject({ logout: false });
Then in your action creator code you would do:
function fetchWithAuthAction(url, method, data) {
return function (dispatch) {
return fetchWithAuth(url, method, data).then(
({ data, headers }) => dispatch(fetchedData(data, headers)),
({ logout }) => {
if(logout) {
dispatch(UserActions.logout());
} else {
dispatch(fetchedDataFailed());
}
);
};
}
Edit:
If you don't want to write the error handling code everywhere, you could create a helper:
function logoutOnError(promise, dispatch) {
return promise.catch(({ logout }) => {
if(logout) {
dispatch(UserActions.logout());
}
})
}
Then you could just use it in your action creators:
function fetchUsers() {
return function (dispatch) {
return logoutOnError(fetchWithAuth("/users", "GET"), dispatch).then(...)
}
}
You can also use axios (interceptors) or apisauce (monitors) and intercept all calls before they goes to their handlers and at that point use the
// this conditional depends on how the interceptor works on each api.
// In apisauce you use response.status
if (response.status === '401') {
store.dispatch(UserActions.logout())
}