SergSam
SergSam

Reputation: 365

Authorize React Components for Users by Role/Permissions

I made a SPA with React and ASP .Net Core. The Authentication is done via Bearer-Tokens (using IdentityServer4 and oidc-client) and Authorization is done on the Server Side (using PolicyServer).

Sofar I can:

In my App after logging in, there are multiple Buttons visible, clicking them opens a Component. My Problem is: I cant find a good way to authorize Users the access to these Components, after pressing a Button.

I need to check if the User has the required Role/Permission or alternatively Claims.

After not finding any way to do this I thought about using axios to check a HttpGet which returns if User is authorized or not. But having an axios-command on client-side is very likely not an good way.

Could anyone help please?

Upvotes: 0

Views: 5090

Answers (3)

Using IdentityServer and .Net Core identity, you can do the following:

  1. Add the role to the token meaning insert a role claim to the issued claims by registering a transient service in Startup.cs.

  2. Now when you call the user.profile in AuthorizeService.js you will notice the added property "role", all you have to do now is to validate and render your component based on that role/permission.

  3. Let us say you need to only give 'Member' access to a certain component:

     export class NavMenu extends Component {
    static displayName = NavMenu.name;
    
       constructor(props) {
         super(props);
    
         this.toggleNavbar = this.toggleNavbar.bind(this);
         this.state = {
           collapsed: true,
           isAuthenticated: false,
           role: null,
         };
       }
       componentDidMount() {
         this._subscription = authService.subscribe(() => this.populateState());
         this.populateState();
       }
    
       componentWillUnmount() {
         authService.unsubscribe(this._subscription);
       }
       async populateState() {
         const [isAuthenticated, user] = await Promise.all([
           authService.isAuthenticated(),
           authService.getUser(),
         ]);
         this.setState({
           isAuthenticated,
           role: user && user.role,
         });
       }
    
       toggleNavbar() {
         this.setState({
           collapsed: !this.state.collapsed,
         });
       }
    
       render() {
         return (
           <header>
             <Navbar
               className="navbar-expand-sm navbar-toggleable-sm ng-white border-bottom box-shadow mb-3"
               light
             >
               <Container>
                 <NavbarBrand tag={Link} to="/">
                   SPA.Utility.Templates
                 </NavbarBrand>
                 <NavbarToggler onClick={this.toggleNavbar} className="mr-2" />
                 <Collapse
                   className="d-sm-inline-flex flex-sm-row-reverse"
                   isOpen={!this.state.collapsed}
                   navbar
                 >
                   <ul className="navbar-nav flex-grow">
                     <NavItem>
                       <NavLink tag={Link} className="text-dark" to="/">
                         Home
                       </NavLink>
                     </NavItem>
    
                     {this.state.role && this.state.role.includes("Member") ? (
                       <NavItem>
                         <NavLink tag={Link} className="text-dark" to="/counter">
                           Counter
                         </NavLink>
                       </NavItem>
                     ) : null}
                     <NavItem>
                       <NavLink tag={Link} className="text-dark" to="/fetch-data">
                         Fetch data
                       </NavLink>
                     </NavItem>
    
                     <LoginMenu></LoginMenu>
                   </ul>
                 </Collapse>
               </Container>
             </Navbar>
           </header>
         );
       }
     }
    

notice the part when you render a whole component depending on the role of the user:

{this.state.role && this.state.role.includes("Member") ? (
               <NavItem>
                 <NavLink tag={Link} className="text-dark" to="/counter">
                   Counter
                 </NavLink>
               </NavItem>
             ) : null}

Here is a sample application using RBAC in .Net core3.1 and reactJs that might help you: https://github.com/HoussemMereghni/SPA-NetCore-React-RBAC

Upvotes: 1

Meet Zaveri
Meet Zaveri

Reputation: 3059

I personally use HOC(Higer Order Component)/Wrapper for authorization for routes.

What does HOC does?

  • We pass the required component for respective route to HOC and additional data required. In route's component prop pass HOC as component={WithAuthorization(MyComponent,miscData) />

  • It allows us to render component conditionally. In a nutshell if authorization logic fails, then render fallback Component or render the actual component passed to it

My typical HOC would look like:

export const WithAuthorization = (Component, menuObj) => {
    /* For route's data, you can have menu's metadata */
    const { menuId, action, menuName } = menuObj;
    class UserMode extends React.Component {
        state = {
            isAllowedToAccess: false,
            isProcessCompleted: false,
            menuPermissionObj: {},
            masterAccess: false
        };

        async componentDidMount() {
            const permission = this.props.auth.menuPermissions
                ? this.props.auth.menuPermissions
                : JSON.parse(localStorage.getItem('menu_permissions'));
            await this.checkAuthorization(permission);
        }

        shouldComponentUpdate(nextProps, nextState) {
            /* 
            Restrict re-render until there is 
            menuPermissions object available for user profile
            */
            if (this.props.auth.menuPermissions) {
                return true;
            } else {
                return false;
            }
        }

        /* Business logic to check roles & permissions for the user and restrict them */
        checkAuthorization = permissionArr => {
            let access = false;
            let menuPermissionObj = {};

            /* PERFORM YOUR BUSINESS LOGIC HERE */
            function apiCall(){
            /* return true or false */
            }
            access = apiCall(); // either true or false depending on API call

            console.log('has access', access);
            this.setState({
                isAllowedToAccess: access,
                isProcessCompleted: true,
                menuPermissionObj
            });
        };

        render() {
            const {
                isProcessCompleted,
                isAllowedToAccess,
                menuPermissionObj,
                masterAccess
            } = this.state;
            return isProcessCompleted ? (
                isAllowedToAccess ? (
                    <Fragment>
                        <Component
                            {...this.props}
                            menuAccess={menuPermissionObj}
                            masterAccess={masterAccess}
                        />
                    </Fragment>
                ) : (
                    <Redirect to="/dashboard" />
                )
            ) : (
                <Loading state={this.state} />
            );
        }
    }

    /* OPTIONAL: Use react-redux's connect if you want to access redux's store data */

    return UserMode;
};

Upvotes: 2

Anton Bks
Anton Bks

Reputation: 492

You can setup protected routes using react router. Check https://tylermcginnis.com/react-router-protected-routes-authentication/ for more info.

Upvotes: 0

Related Questions