I am trying to figure out the structure of my jwt token authentication with react/redux. The way I have it set up now, I use a middleware that saves the token in localstorage when the action ATTEMPT_LOGIN_SUCCESS is dispatched (Note: The security of localstorage is besides the point).
For the initial state of whether the user is logged in, I currently get the token from local storage and verify it inside of my login reducer. My question is whether or not that is a place that I should indeed put that logic.
jwt-handler middleware for saving token:
import {ATTEMPT_LOGIN_SUCCESS} from "./dataTypes/login"
const jwtHandler = store => next => action => {
switch(action.type){
case ATTEMPT_LOGIN_SUCCESS:
const token = action.payload;
localStorage.setItem("token", token);
}
return next(action);
}
export default jwtHandler;
Login reducer (code in question is from the top to the initial state):
import {
ATTEMPT_LOGIN_BEGIN, ATTEMPT_LOGIN_FAIL, ATTEMPT_LOGIN_SUCCESS
} from "../dataTypes/login";
import authenticateToken from "./authenticateToken";
const token = localStorage.getItem("token");
const isValid = authenticateToken(token); //Decodes and checks date to ensure valid
if(!isValid){
localStorage.setItem("token", "");
}
const initialState = {
loading:false,
errorMessage: "",
success:false,
isLoggedIn: isValid
};
const loginReducer = (state = initialState, action) => {
switch (action.type) {
case ATTEMPT_LOGIN_BEGIN:
return {
...state,
loading:true,
errorMessage:""
};
case ATTEMPT_LOGIN_SUCCESS:
return {
...state,
loading:false,
success:true,
token:action.payload
};
case ATTEMPT_LOGIN_FAIL:
return {
...state,
loading:false,
errorMessage:action.payload
};
default:
return state;
}
};
export default loginReducer;
I feel like there may be a better place to put my is logged in logic, but I have yet to find a good answer online.
I prefer your middleware pattern, but bit more centralised:
NOTE: this is opinionated and just an example sketch
// middleware.js
import {
INIT_SESSION, ATTEMPT_LOGIN_BEGIN, ATTEMPT_LOGIN_FAIL, ATTEMPT_LOGIN_SUCCESS, LOGOUT
} from "../dataTypes/login";
import authenticateToken from "./authenticateToken";
const jwtHandler = store => next => action => {
switch(action.type){
// INIT_SESSION handled only here not in any reducer
case INIT_SESSION:
const token = localStorage.getItem("token");
const isValid = authenticateToken(token);
if(valid){
store.dispatch({type:ATTEMPT_LOGIN_SUCCESS, payload: token});
} else {
localStorage.removeItem("token");
}
break;
case ATTEMPT_LOGIN_SUCCESS:
const newToken = action.payload;
localStorage.setItem("token", newToken);
break;
case LOGOUT:
localStorage.removeItem("token");
break;
default:
}
return next(action);
}
export default jwtHandler;
// index.js
const store = createStore();
store.dispatch({type: INIT_SESSION});
ReactDOM.render(<Provider store={store}>//...
// reducer.js stays clean...