Как обеспечить выполнение проверки JWT перед перенаправлением маршрутизатора React / Redux?

#reactjs #redux #jwt #redux-saga #feathersjs

#reactjs #redux #jwt #redux-saga #feathersjs

Вопрос:

Я разрабатываю приложение PERN с полным стеком, используя React / Redux, Knex возражение.Js PostgreSQL для БД и feathersjs для платформы API. Таким образом, я @feathersjs/client также использую интерфейс и их пакет аутентификации. Я также использую connected-react-router для своей маршрутизации. К сожалению, всякий раз, когда я пытаюсь перейти к защищенному маршруту, запрос «login», отвечающий за настройку состояния пользователя (из их аутентификации jwt на сервере), не завершается до того, как перенаправление приведет пользователя на страницу входа.

Я проверяю jwt в index.js файле приложения react, отправляя действие.

 if (localStorage['feathers-jwt']) {
  try {
       store.dispatch(authActions.login({strategy: 'jwt', accessToken: localStorage.getItem('feathers-jwt')}));
  }
  catch (err){
      console.log('authenticate catch', err);
  }
}
  

Выбирается действие, с помощью redux-saga которого выполняется следующее действие

 export function* authSubmit(action) {
  console.log('received authSubmit');
  try {
    const data = yield call(loginApi, action);
    yield put({type: authTypes.LOGIN_SUCCESS, data});

  } catch (error) {
      console.log(error);
      yield put({type: authTypes.LOGIN_FAILURE, error})
  }
}

function loginApi(authParams) {
  return services.default.authenticate(authParams.payload)
}
  

Вот моя isAuthenticated функция с объектом конфигурации:

 const isAuthenticated =  connectedReduxRedirect({
  redirectPath: '/login',
  authenticatedSelector: state => state.auth.user !== null,
  redirectAction: routerActions.replace,
  wrapperDisplayName: 'UserIsAuthenticated'
});
  

Вот HOC, применяемый к компонентам контейнера

 const Login = LoginContainer;
const Counter = isAuthenticated(CounterContainer);
const LoginSuccess = isAuthenticated(LoginSuccessContainer);
  

И, наконец, вот рендеринг

 export default function (store, history) {
  ReactDOM.render(
    <Provider store={store}>
      <ConnectedRouter history={history}>
        <Switch>
          <Route exact={true} path="/" component={App}/>
          <Route path="/login" component={Login}/>
          <Route path="/counter" component={Counter}/>
          <Route path="/login-success" component={LoginSuccess}/>
          <Route component={NotFound} />
        </Switch>
      </ConnectedRouter>
    </Provider>,
    document.getElementById('root')
  );
}
  

Например, при входе в систему и посещении я ожидаю, что произойдет /counter следующее

  1. Запущено действие LOGIN_REQUEST

  2. Действие LOGIN_SUCCESS запущено, пользователь аутентифицируется JWT

  3. маршрутизатор видит, что объект user.auth не равен нулю, поэтому пользователь аутентифицируется

  4. маршрутизатор разрешает навигацию без перенаправления

Вместо этого я вижу следующее (при переходе вручную к /counter )

  1. @@INIT

  2. auth / LOGIN_REQUEST [это хорошо, loggingIn: true ]

  3. @@router /LOCATION_CHANGE

 {
  type: '@@router/LOCATION_CHANGE',
  payload: {
    location: {
      pathname: '/counter',
      search: '',
      hash: ''
    },
    action: 'POP',
    isFirstRendering: true
  }
}
  
  1. @@router_LOCATION_CHANGE [это проблема]
   type: '@@router/LOCATION_CHANGE',
  payload: {
    location: {
      pathname: '/login',
      hash: '',
      search: '?redirect=/counter',
      key: 'kdnf4l'
    },
    action: 'REPLACE',
    isFirstRendering: false
  }
}
  
  1. Пользователь переходит к /login , который регистрирует пользователя в том виде, в котором он разработан в настоящее время.

  2. LOGOUT_REQUEST -> LOGIN_SUCCESS -> LOCATION_CHANGE (to /login-success )

Опять же, любая помощь была бы очень признательна, и я могу предоставить все остальное по мере необходимости.

Спасибо!

-Бренден

Ответ №1:

Решение

Сегодня я смог решить эту проблему, взглянув на то, как feathers-reduxify-authentication функционирует пакет аутентификации. Перенаправление было, по большей части, настроено правильно.

СЕРВЕРНАЯ ЧАСТЬ

authentication.js

Обратите внимание на несколько стратегий и на то, как возвращается context.result . Это необходимо для feathers-reduxify-authentication правильной работы.

 module.exports = function (app) {
  const config = app.get('authentication');

  // Set up authentication with the secret
  app.configure(authentication(config));
  app.configure(jwt());
  app.configure(local(config.local));


  app.service('authentication').hooks({
    before: {
      create: [
        authentication.hooks.authenticate(config.strategies),
      ],
      remove: [
        authentication.hooks.authenticate('jwt')
      ]
    },
    after: {
      create: [
        context => {
          context.result.data = context.params.user;
          context.result.token = context.data.accessToken;
          delete context.result.data.password;
          return context;
        }
      ]
    }
  });
};
  

ИНТЕРФЕЙС

src/feathers/index.js

Это соответствует примерному проекту eddystop, но обновлено до feathers 3.0

 import feathers from '@feathersjs/client';
import  io  from 'socket.io-client';
import reduxifyAuthentication from 'feathers-reduxify-authentication';
import reduxifyServices, { getServicesStatus } from 'feathers-redux';
import { mapServicePathsToNames, prioritizedListServices } from './feathersServices';
const hooks = require('@feathersjs/client');

const socket = io('http://localhost:3030');
const app = feathers()
  .configure(feathers.socketio(socket))
  .configure(hooks)
  .configure(feathers.authentication({
    storage: window.localStorage
  }));
export default app;

// Reduxify feathers-client.authentication
export const feathersAuthentication = reduxifyAuthentication(app,
  { authSelector: (state) => state.auth.user}
);
// Reduxify feathers services
export const feathersServices = reduxifyServices(app, mapServicePathsToNames);
export const getFeathersStatus =
  (servicesRootState, names = prioritizedListServices) =>
    getServicesStatus(servicesRootState, names);
  

промежуточное программное обеспечение и хранилище. src/state/configureStore

redux-saga временно удален, я верну его, как только закончу тестирование

 import { createBrowserHistory } from 'history';
import { createStore, applyMiddleware, compose } from "redux";
import { routerMiddleware  } from 'connected-react-router';
import createRootReducer from './ducks';
import promise  from 'redux-promise-middleware';
import reduxMulti from 'redux-multi';
import rootSaga from '../sagas';
import createSagaMiddleware from 'redux-saga';
export default function configureStore(initialState) {

    const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
        || compose;

    const middlewares = [
        //sagaMiddleware,
        promise,
        reduxMulti,
        routerMiddleware(history)];

    const store = createStore(
        createRootReducer(history),
        initialState,
        composeEnhancer(
            applyMiddleware(
                ...middlewares
            )
        )
    );

    return store;
}
  

корневые редукторы, src/state/ducks/index.js

 import { combineReducers } from "redux";
import { connectRouter } from 'connected-react-router';
import { reducer as reduxFormReducer } from 'redux-form';
import {feathersAuthentication, feathersServices} from '../../feathers';
import counter from './counter';

const rootReducer = (history) => combineReducers({
    counter,
    router: connectRouter(history),
    users: feathersServices.users.reducer,
    auth: feathersAuthentication.reducer,
    form: reduxFormReducer, // reducers required by redux-form

});

export default rootReducer;