最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - ReactJS auth redirect before application loads - Stack Overflow

programmeradmin0浏览0评论

I am designing a React application consisting of multiple ponents.

In my organisation they use a kind of Authentication mechanism wherein one needs to check a particular cookie; if available user is considered authenticated and is allowed to view app. If cookie is not there / expired then one needs to direct the user to a particular URL wherein he can fill in his user id and password and then that URL redirects him to original application along with valid cookie.

I am thinking how this can be achieved in my React application... I know there is ponentWillMount method but shouldn’t user first get authenticated before any of the ponent loads?

How to implement this?

Guidance appreciated

Cheers

I am designing a React application consisting of multiple ponents.

In my organisation they use a kind of Authentication mechanism wherein one needs to check a particular cookie; if available user is considered authenticated and is allowed to view app. If cookie is not there / expired then one needs to direct the user to a particular URL wherein he can fill in his user id and password and then that URL redirects him to original application along with valid cookie.

I am thinking how this can be achieved in my React application... I know there is ponentWillMount method but shouldn’t user first get authenticated before any of the ponent loads?

How to implement this?

Guidance appreciated

Cheers

Share Improve this question asked Oct 26, 2017 at 10:36 Akshay LokurAkshay Lokur 7,51615 gold badges48 silver badges68 bronze badges 2
  • How about just put the function in your entry point of js (e.g. index.js)? But it will block your full application running & rendering – Stanley Cheung Commented Oct 26, 2017 at 10:40
  • see Redux, it's store may help you control the login state of a user – KAngel7 Commented Oct 26, 2017 at 11:07
Add a ment  | 

2 Answers 2

Reset to default 6

If you are using react-router you have to create protected route ponent, which checks if user is authenticated.

Then your routes file should look like this:

import { Route, Redirect, BrowserRouter } from 'react-router-dom'

    const ProtectedRoute = ({ ponent: Component, ...rest }) => (
      <Route {...rest} render={props => (
        auth.isAuthenticated ? (
          <Component {...props}/>
        ) : (
          <Redirect to={{
            pathname: '/login',
            state: { from: props.location }
          }}/>
        )
      )}/>
    )

export const Routes = () => (

    <BrowserRouter>
      <Route path='/' ponent={Index}/>
      <ProtectedRoute path='/access' ponent={Access}/>
    </BrowserRouter>

)

For more details check the official example on auth redirects.

You have several cases of handling authentication redirection / token expiration.

1. At start time

  1. Wait for the redux-persist to finish loading and injecting in the Provider ponent
  2. Set the Login ponent as the parent of all the other ponents
  3. Check if the token is still valid 3.1. Yes: Display the children 3.2. No: Display the login form

2. When the user is currently using the application

You should use the power of middlewares and check the token validity in every dispatch the user makes.

If the token is expired, dispatch an action to invalidate the token. Otherwise, continue as if nothing happened.

Take a look at the middleware token.js below.


I wrote a whole sample of code for your to use and adapt it if needed.

The solution I propose below is router agnostic. You can use it if you use react-router but also with any other router.

App entry point: app.js

See that the Login ponent is on top of the routers

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 };
  }

  ponentWillMount() {
    persistReduxStore(store)(() => this.setState({ rehydrated: true }));
  }

  render() {
    const history = syncHistoryWithStore(browserHistory, store);
    return (
      <Provider store={store}>
        <Login>
          {createRoutes(history)}
        </Login>
      </Provider>
    );
  }
}

store.js

The key to remember here is to use redux-persist and keep the login reducer in the local storage (or whatever storage).

import { createStore, applyMiddleware, pose, bineReducers } 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 = bineReducers({
  ...reducers,
  routing: routerReducer,
});

export const initStore = (state) => {
  const poseEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || pose;
  const store = createStore(
    reducer,
    {},
    poseEnhancers(
      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

This is a middleware to add in order to check wether the token is still valid.

If the token is no longer valid, a dispatch is trigger to invalidate it.

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);
  };
}

Login Component

The most important thing here is the test of if (!login.isInvalidated).

If the login data is not invalidated, it means that the user is connected and the token is still valid. (Otherwise it would have been invalidated with the 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);

Login actions

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',
  };
}

Login reducers

import { bineReducers } from 'redux';
import assign from 'lodash/assign';
import jwtDecode from 'jwt-decode';

export default bineReducers({
  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;
  }
}

Hope it helps.

发布评论

评论列表(0)

  1. 暂无评论