Best practice for React Router user roles (Firebas

2020-07-18 07:50发布

问题:

My application will have 2 roles, Employee and Admin. I'm trying to implement middleware so users get redirected if they are not authorized to see the content. Is handling not just general authentication but also user roles in React Router good practice?

My first thought was to add a custom role attribute to firebase.auth().currentUser, but adding attributes to currentUser is not allowed by firebase.

If so, how would I do it? Through state or fetching it from my Firebase DB like this?:

var requireEmp = (nextState, replace, next) => {
 var role;
 var uid = firebase.auth().currentUser.uid;
 firebase.database().ref('/users/' + uid + '/role').once('value').then((user) => {
  role = user.val().role;
 });
 if (role !== 'employee') {
  replace('/');     
 }
 next();
};

...

<Router history={hashHistory}>
 <Route path="/" >
  <Route path="home" component={Main} onEnter={requireLogin}>
    <Route path="work" component={Work} onEnter={requireEmp}/>
    <Route path="profile" component={Profile} />
    <IndexRoute component={Profile}/>
  </Route>
 </Route>
</Router>

I'm new to React and Redux and still a bit scared of working with state and important data such as the user role attribute.

What are some other areas I need to be really careful with concerning the implementation of user roles?

Thanks.

回答1:

lets get that user roles working! Every project has its specificities, but here's how I would do it:

Before you first render your app, you've got to be sure that firebase user/currentUser/currentAuth has loaded. If you have roles, just make sure to fetch it it the user is logged in.

Here's an example:

On index.jsx:

import { initializeApp } from './myFirebase';

const routes = routesConfig(store);

let appHasRendered = false;

const renderAppOnce = () => {
  if (appHasRendered) return;

  render(
    <Provider store={store}>
      <Router history={syncedHistory} routes={routes} />
    </Provider>,
    document.getElementById('app')
  );

  appHasRendered = true;
};

initializeApp(renderAppOnce, store.dispatch);

and then on myFirebase.js:

export const initializeApp = (renderAppOnce, dispatch) => {
  firebaseAuth.onAuthStateChanged((user) => {

    if (user) {
      // We have a user, lets send him and his role to the store

      firebaseRef.child('users/roles').once('value', (snap) => {
        dispatch(authLoggedIn({ 
          ...user.toJSON(), 
          role: snap.val() || 'employee'
        }));
        renderAppOnce();
      });

    } else {
      // There's no user, let's move on
      dispatch(authLoggedOut());
      renderAppOnce();
    }
  });
};

All right!!! We have all we need in our store. So now we just have to check that on our onEnter functions of our app:

const routesConfig = (store) => {
  // Confirms user is not authenticated
  const confirmNoAuth = (nextState, replace) => {
    if (store.getState().user) {
      replace({ pathname: '/', state: { nextPathname: nextState.location.pathname } });
    }
  };

  // Confirms user is authenticated
  const confirmAuth = (nextState, replace) => {
    if (!store.getState().user) {
      replace({ pathname: '/', state: { nextPathname: nextState.location.pathname } });
    }
  };

  // Confirms user has a specific role
  const confirmRole = role => ((nextState, replace) => {
    if (store.getState().user.role !== role) {
      replace({ pathname: '/', state: { nextPathname: nextState.location.pathname } });
    }
  });

  return (<Route path="/">
    <IndexRoute component={HomePage} />
    <Route path="login" component={LoginPage} onEnter={confirmNoAuth} />
    <Route path="dasboard" component={DashboardPage} onEnter={confirmAuth} />
    <Route path="adminsonly" component={AdminDashboardPage} onEnter={confirmRole('admin')} />
  </Route>);
};

Theres probably tons of problems on this code, but I believe you can understand the principles. Basically you should pre-fetch the role so you don't have to do it on every route change.

One other tip I can give you is that if you'll have tons of employees and just a handful of admins, just save the admins. This way you'll only have like 20 entries on your roles object instead of hundreds of thousands. That tiny || 'employees' can save you lots of space.

Keep in mind that you can just as easily add more roles if you need. Also, this example uses Redux, but you don't have to.

!!! IMPORTANT !!!

All of this will only keep people from accessing the pages, but smartypants can use the console or a rest client to try to stick their noses in parts of your database where they shouldn't! Be sure to understand and make good use of firebase rules to keep your database secure!

Let me know if it worked