javascript - metodo - En términos de un cliente front-end sin estado, ¿qué tan segura es esta lógica JWT?
message falta definir el token (1)
No creo que sea importante para esta pregunta cuál es mi configuración exacta, pero acabo de notar esto en mis aplicaciones React y React Native, y de repente me di cuenta de que en realidad no están verificando ningún tipo de validez del JWT que se almacena .
Aquí está el código:
const tokenOnLoad = localStorage.getItem(''token'')
if (tokenOnLoad) store.dispatch({ type: AUTH_USER })
Probablemente no sea realmente un problema, porque el token se adjunta a los encabezados y el servidor ignorará cualquier solicitud sin un token válido, pero ¿hay alguna manera de que pueda actualizar esto para que sea mejor (es decir, más segura y menos posibilidades de cargar la IU? que detona debido a token malformado o si alguien hackeó su propia ''token''
)?
Aquí está el token que se adjunta a cada solicitud:
networkInterface.use([{
applyMiddleware(req, next) {
if (!req.options.headers) req.options.headers = {}
const token = localStorage.getItem(''token'')
req.options.headers.authorization = token || null
next()
}
}])
¿Debo agregar algo de lógica para al menos verificar la longitud del token o decodificarlo y verificar si tiene una identificación de usuario? ¿O es un desperdicio de CPU y tiempo cuando el servidor lo hace?
Solo estoy buscando si hay formas de bajo costo para validar aún más el token y endurecer la aplicación.
También uso un componente de orden superior requireAuth()
que echa a los usuarios si no están conectados. Siento que podría haber algún UX incorrecto si la aplicación de alguna manera hiciera localStorage.setItem(''token'', ''lkjashkjdf'')
.
Su solución no es óptima, ya que afirmó que realmente no verifica la validez del token del usuario.
Déjame detallar cómo puedes manejarlo:
1. Comprobación del token en el momento del inicio
- Espere a que
redux-persist
elredux-persist
para terminar de cargar e inyectar en el componenteProvider
- Establezca el componente de inicio de sesión como principal de todos los demás componentes
- Verifica si el token sigue siendo válido 3.1. Sí: muestra los niños 3.2. No: muestre el formulario de inicio de sesión
2. Cuando el usuario está utilizando actualmente la aplicación
Debería usar el poder de los middlewares y verificar la validez del token en cada dispatch
haga el usuario.
Si el token está vencido, envíe una acción para invalidar el token. De lo contrario, continúe como si nada hubiera sucedido.
Eche un vistazo al middleware token.js
continuación.
Escribí una muestra completa de código para que lo use y lo adapte si es necesario.
La solución que propongo a continuación es independiente del enrutador. Puede usarlo si usa react-router
pero también con cualquier otro enrutador.
Punto de entrada de la aplicación: app.js
Verifique que el componente de Login
esté en la parte superior de los enrutadores
import React from ''react'';
import { Provider } from ''react-redux'';
import { browserHistory } from ''react-router'';
import { syncHistoryWithStore } from ''react-router-redux'';
import createRoutes from ''./routes''; // Contains the routes
import { initStore, persistReduxStore } from ''./store'';
import { appExample } from ''./container/reducers'';
import Login from ''./views/login'';
const store = initStore(appExample);
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = { rehydrated: false };
}
componentWillMount() {
persistReduxStore(store)(() => this.setState({ rehydrated: true }));
}
render() {
const history = syncHistoryWithStore(browserHistory, store);
return (
<Provider store={store}>
<Login>
{createRoutes(history)}
</Login>
</Provider>
);
}
}
store.js
La clave para recordar aquí es usar redux-persist
y mantener el reductor de inicio de sesión en el almacenamiento local (o en cualquier almacenamiento).
import { createStore, applyMiddleware, compose, combineReducers } from ''redux'';
import { persistStore, autoRehydrate } from ''redux-persist'';
import localForage from ''localforage'';
import { routerReducer } from ''react-router-redux'';
import reducers from ''./container/reducers'';
import middlewares from ''./middlewares'';
const reducer = combineReducers({
...reducers,
routing: routerReducer,
});
export const initStore = (state) => {
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
{},
composeEnhancers(
applyMiddleware(...middlewares),
autoRehydrate(),
),
);
persistStore(store, {
storage: localForage,
whitelist: [''login''],
});
return store;
};
export const persistReduxStore = store => (callback) => {
return persistStore(store, {
storage: localForage,
whitelist: [''login''],
}, callback);
};
Middleware: token.js
Este es un middleware para agregar para verificar si el token sigue siendo válido.
Si el token ya no es válido, se desencadena un envío para invalidarlo.
import jwtDecode from ''jwt-decode'';
import isAfter from ''date-fns/is_after'';
import * as actions from ''../container/actions'';
export default function checkToken({ dispatch, getState }) {
return next => (action) => {
const login = getState().login;
if (!login.isInvalidated) {
const exp = new Date(jwtDecode(login.token).exp * 1000);
if (isAfter(new Date(), exp)) {
setTimeout(() => dispatch(actions.invalidateToken()), 0);
}
}
return next(action);
};
}
Componente de inicio de sesión
Lo más importante aquí es la prueba de if (!login.isInvalidated)
.
Si los datos de inicio de sesión no se invalidan, significa que el usuario está conectado y que el token sigue siendo válido. (De lo contrario, se habría invalidado con el middleware token.js
)
import React from ''react'';
import { connect } from ''react-redux'';
import * as actions from ''../../container/actions'';
const Login = (props) => {
const {
dispatch,
login,
children,
} = props;
if (!login.isInvalidated) {
return <div>children</div>;
}
return (
<form onSubmit={(event) => {
dispatch(actions.submitLogin(login.values));
event.preventDefault();
}}>
<input
value={login.values.email}
onChange={event => dispatch({ type: ''setLoginValues'', values: { email: event.target.value } })}
/>
<input
value={login.values.password}
onChange={event => dispatch({ type: ''setLoginValues'', values: { password: event.target.value } })}
/>
<button>Login</button>
</form>
);
};
const mapStateToProps = (reducers) => {
return {
login: reducers.login,
};
};
export default connect(mapStateToProps)(Login);
Acciones de login
export function submitLogin(values) {
return (dispatch, getState) => {
dispatch({ type: ''readLogin'' });
return fetch({}) // !!! Call your API with the login & password !!!
.then((result) => {
dispatch(setToken(result));
setUserToken(result.token);
})
.catch(error => dispatch(addLoginError(error)));
};
}
export function setToken(result) {
return {
type: ''setToken'',
...result,
};
}
export function addLoginError(error) {
return {
type: ''addLoginError'',
error,
};
}
export function setLoginValues(values) {
return {
type: ''setLoginValues'',
values,
};
}
export function setLoginErrors(errors) {
return {
type: ''setLoginErrors'',
errors,
};
}
export function invalidateToken() {
return {
type: ''invalidateToken'',
};
}
Reductores de inicio de sesión
import { combineReducers } from ''redux'';
import assign from ''lodash/assign'';
import jwtDecode from ''jwt-decode'';
export default combineReducers({
isInvalidated,
isFetching,
token,
tokenExpires,
userId,
values,
errors,
});
function isInvalidated(state = true, action) {
switch (action.type) {
case ''readLogin'':
case ''invalidateToken'':
return true;
case ''setToken'':
return false;
default:
return state;
}
}
function isFetching(state = false, action) {
switch (action.type) {
case ''readLogin'':
return true;
case ''setToken'':
return false;
default:
return state;
}
}
export function values(state = {}, action) {
switch (action.type) {
case ''resetLoginValues'':
case ''invalidateToken'':
return {};
case ''setLoginValues'':
return assign({}, state, action.values);
default:
return state;
}
}
export function token(state = null, action) {
switch (action.type) {
case ''invalidateToken'':
return null;
case ''setToken'':
return action.token;
default:
return state;
}
}
export function userId(state = null, action) {
switch (action.type) {
case ''invalidateToken'':
return null;
case ''setToken'': {
const { user_id } = jwtDecode(action.token);
return user_id;
}
default:
return state;
}
}
export function tokenExpires(state = null, action) {
switch (action.type) {
case ''invalidateToken'':
return null;
case ''setToken'':
return action.expire;
default:
return state;
}
}
export function errors(state = [], action) {
switch (action.type) {
case ''addLoginError'':
return [
...state,
action.error,
];
case ''setToken'':
return state.length > 0 ? [] : state;
default:
return state;
}
}
No dude en hacerme cualquier pregunta o si necesita que le explique más sobre la filosofía.