Hide some React children based on user role

I am writing a one-page application in React and Redux (with the Node.js backend).

I want to implement role-based access control and want to control the display of certain parts (or sub-parts) of the application.

I am going to get a list of permissions from Node.js, which is just an object with this structure:

{ users: 'read', models: 'write', ... dictionaries: 'none', } 

a key is a secure resource

The value is the user's permission for this resource (one of: none , read , write ).

I keep it in redux state. Seems easy enough.

none permission will be checked using react-router routes onEnter/onChange interceptors or redux-auth-wrapper . It also seems easy.

But what is the best way to apply read/write permissions to any component view (for example, hide the edit button in the Model component if the user has permission { models: 'read' } ).

I found this solution and slightly modified it for my task:

 class Check extends React.Component { static propTypes = { resource: React.PropTypes.string.isRequired, permission: React.PropTypes.oneOf(['read', 'write']), userPermissions: React.PropTypes.object, }; // Checks that user permission for resource is the same or greater than required allowed() { const permissions = ['read', 'write']; const { permission, userPermissions } = this.props; const userPermission = userPermissions[resource] || 'none'; return permissions.indexOf(userPermission) >= permissions.indexOf(permission) } render() { if (this.allowed()) return { this.props.children }; } } export default connect(userPermissionsSelector)(Check) 

where userPermissionsSelector would be something like this: (store) => store.userPermisisons and return the user rights object.

Then wrap the protected Check element:

 <Check resource="models" permission="write"> <Button>Edit model</Button> </Check> 

therefore, if the user does not have write permission for models , the button will not be displayed.

Has anyone done something like this? Is there a more β€œelegant” solution than this?

thanks!

PS Of course, user permission will also be checked on the server side.

+7
reactjs redux permissions roles
source share
1 answer

Well, I think I understand what you want. I have done something that works for me, and I like the way I have it, but I understand that there are other viable solutions.

What I wrote was a HOC-style jet router.

Basically, I have PermissionsProvider where I run user permissions. I have another withPermissions HOC that introduces the permissions granted earlier to my component.

Therefore, if I ever need to check permissions on this particular component, I can easily access them.

 // PermissionsProvider.js import React, { Component } from "react"; import PropTypes from "prop-types"; import hoistStatics from "hoist-non-react-statics"; class PermissionsProvider extends React.Component { static propTypes = { permissions: PropTypes.array.isRequired, }; static contextTypes = { permissions: PropTypes.array, }; static childContextTypes = { permissions: PropTypes.array.isRequired, }; getChildContext() { // maybe you want to transform the permissions somehow // maybe run them through some helpers. situational stuff // otherwise just return the object with the props.permissions // const permissions = doSomething(this.props.permissions); // maybe add some validation methods return { permissions: this.props.permissions }; } render() { return React.Children.only(this.props.children); } } const withPermissions = Component => { const C = (props, context) => { const { wrappedComponentRef, ...remainingProps } = props; return ( <Component permissions={context.permissions} {...remainingProps} ref={wrappedComponentRef} /> ); }; C.displayName = `withPermissions(${Component.displayName || Component.name})`; C.WrappedComponent = Component; C.propTypes = { wrappedComponentRef: PropTypes.func }; C.contextTypes = { permissions: PropTypes.array.isRequired }; return hoistStatics(C, Component); }; export { PermissionsProvider as default, withPermissions }; 

Ok, I know this is a lot of code. But this is HOC (you can find out more here ).

Higher Order Component (HOC) is the best practice in React for reusing component logic. HOCs are not part of the React API as such. They are a sample that arises from a representative composition. Specifically, a higher-order component is a function that takes a component and returns a new component.

Basically, I did this because I was inspired by the reaction of the router. Whenever you want to know some routing materials, you can simply add the @withRouter decorator and they will add the details to your component. So why not do the same?

  • You must first set permissions on the provider
  • Then you use the withPermissions decorator on components that check permissions

 //App render return ( <PermissionsProvider permissions={permissions}> <SomeStuff /> </PermissionsProvider> ); 

Somewhere inside SomeStuff , do you have a widespread toolbar that checks permissions?

 @withPermissions export default class Toolbar extends React.Component { render() { const { permissions } = this.props; return permissions.canDoStuff ? <RenderStuff /> : <HeCantDoStuff />; } } 

If you cannot use decorators, you export a toolbar, for example,

 export default withPermissions(Toolbar); 

Here is the code I showed in practice:

https://codesandbox.io/s/lxor8v3pkz

NOTES:

  • I really simplified the permissions because this logic comes from your goal and for demonstration purposes I simplified them.
  • I assumed that the permissions were an array, so I check PropTypes.array in HOC
  • This is a very long and complicated answer, and I tried to formulate my best abilities. Please do not measure me for some errors here and there :)
+2
source share

All Articles