Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get current user roles with ADAL JS

I have the following react app, which is a template from envato. I have integrated Azure AD authentication using this component and it works perfectly fine:

https://github.com/salvoravida/react-adal

However I want to create ROLES, and I want to be able to show the menu items on the sidebar depending on the roles the current user has.

I already know how to create roles in Azure AD using the app manifest, so this question is more about how to get those roles after the user is authenticated and how to render the menu items depending on the claim value.

The relevant pieces of code here:

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import DashApp from './dashApp';
import registerServiceWorker from './registerServiceWorker';
import 'antd/dist/antd.css';
import { runWithAdal } from 'react-adal';
import { authContext } from './adalConfig';

const DO_NOT_LOGIN = false;
runWithAdal(authContext, () => {
  ReactDOM.render(<DashApp />, document.getElementById('root'));
  // Hot Module Replacement API
  if (module.hot) {
    module.hot.accept('./dashApp.js', () => {
      const NextApp = require('./dashApp').default;
      ReactDOM.render(<NextApp />, document.getElementById('root'));
    });
  }

},DO_NOT_LOGIN);

registerServiceWorker();

AdalConfig.js

import { AuthenticationContext, adalFetch, withAdalLogin } from 'react-adal';

export const adalConfig = {
  tenant: 'abc-af96-4f7c-82db-b6f0bd7ae9b6',
  clientId: 'abc-969c-49b2-8a58-78eece990daf',
  endpoints: {
    api:'abc-083c-4c10-b40f-f1d764319b21'

  'apiUrl': 'https://abc.azurewebsites.net/api',
  cacheLocation: 'localStorage'
};

export const authContext = new AuthenticationContext(adalConfig);

export const adalApiFetch = (fetch, url, options) =>
  adalFetch(authContext, adalConfig.endpoints.api, fetch, adalConfig.apiUrl+url, options);

export const withAdalLoginApi = withAdalLogin(authContext, adalConfig.endpoints.api);

dashboard.js

import React, { Component } from 'react';
import LayoutContentWrapper from '../components/utility/layoutWrapper';
import LayoutContent from '../components/utility/layoutContent';

export default class extends Component {
  render() {
    return (
      <LayoutContentWrapper style={{ height: '100vh' }}>
        <LayoutContent>
          <h1>ISOMORPHIC DASHBOARD HOME</h1>
        </LayoutContent>
      </LayoutContentWrapper>
    );
  }
}

Router.js

import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { ConnectedRouter } from 'react-router-redux';
import { connect } from 'react-redux';

import App from './containers/App/App';
import asyncComponent from './helpers/AsyncFunc';

const RestrictedRoute = ({ component: Component, isLoggedIn, ...rest }) => (
  <Route
    {...rest}
    render={props => isLoggedIn
      ? <Component {...props} />
      : <Redirect
          to={{
            pathname: '/signin',
            state: { from: props.location },
          }}
        />}
  />
);

const PublicRoutes = ({ history, isLoggedIn }) => {
  return (
    <ConnectedRouter history={history}>
      <div>
        <Route
          exact
          path={'/'}
          render={() => <Redirect to="/dashboard" />}
        />
        <Route
          exact
          path={'/signin'}
          component={asyncComponent(() => import('./containers/Page/signin'))}
        />
        <RestrictedRoute
          path="/dashboard"
          component={App}
          isLoggedIn={isLoggedIn}
        />
      </div>
    </ConnectedRouter>
  );
};


export default connect(state => ({
  isLoggedIn: state.Auth.get('idToken') !== null,
}))(PublicRoutes);

sidebar

import React, { Component } from "react";
import { connect } from "react-redux";
import clone from "clone";
import { Link } from "react-router-dom";
import { Layout } from "antd";
import options from "./options";
import Scrollbars from "../../components/utility/customScrollBar.js";
import Menu from "../../components/uielements/menu";
import IntlMessages from "../../components/utility/intlMessages";
import SidebarWrapper from "./sidebar.style";
import appActions from "../../redux/app/actions";
import Logo from "../../components/utility/logo";
import themes from "../../settings/themes";
import { themeConfig } from "../../settings";

const SubMenu = Menu.SubMenu;
const { Sider } = Layout;

const {
  toggleOpenDrawer,
  changeOpenKeys,
  changeCurrent,
  toggleCollapsed
} = appActions;
const stripTrailingSlash = str => {
  if (str.substr(-1) === "/") {
    return str.substr(0, str.length - 1);
  }
  return str;
};

class Sidebar extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
    this.onOpenChange = this.onOpenChange.bind(this);
  }
  handleClick(e) {
    this.props.changeCurrent([e.key]);
    if (this.props.app.view === "MobileView") {
      setTimeout(() => {
        this.props.toggleCollapsed();
        this.props.toggleOpenDrawer();
      }, 100);
    }
  }
  onOpenChange(newOpenKeys) {
    const { app, changeOpenKeys } = this.props;
    const latestOpenKey = newOpenKeys.find(
      key => !(app.openKeys.indexOf(key) > -1)
    );
    const latestCloseKey = app.openKeys.find(
      key => !(newOpenKeys.indexOf(key) > -1)
    );
    let nextOpenKeys = [];
    if (latestOpenKey) {
      nextOpenKeys = this.getAncestorKeys(latestOpenKey).concat(latestOpenKey);
    }
    if (latestCloseKey) {
      nextOpenKeys = this.getAncestorKeys(latestCloseKey);
    }
    changeOpenKeys(nextOpenKeys);
  }
  getAncestorKeys = key => {
    const map = {
      sub3: ["sub2"]
    };
    return map[key] || [];
  };
  getMenuItem = ({ singleOption, submenuStyle, submenuColor }) => {
    const { key, label, leftIcon, children } = singleOption;
    const url = stripTrailingSlash(this.props.url);
    if (children) {
      return (
        <SubMenu
          key={key}
          title={
            <span className="isoMenuHolder" style={submenuColor}>
              <i className={leftIcon} />
              <span className="nav-text">
                <IntlMessages id={label} />
              </span>
            </span>
          }
        >
          {children.map(child => {
            const linkTo = child.withoutDashboard
              ? `/${child.key}`
              : `${url}/${child.key}`;
            return (
              <Menu.Item style={submenuStyle} key={child.key}>
                <Link style={submenuColor} to={linkTo}>
                  <IntlMessages id={child.label} />
                </Link>
              </Menu.Item>
            );
          })}
        </SubMenu>
      );
    }
    return (
      <Menu.Item key={key}>
        <Link to={`${url}/${key}`}>
          <span className="isoMenuHolder" style={submenuColor}>
            <i className={leftIcon} />
            <span className="nav-text">
              <IntlMessages id={label} />
            </span>
          </span>
        </Link>
      </Menu.Item>
    );
  };
  render() {
    const { app, toggleOpenDrawer, height } = this.props;
    const collapsed = clone(app.collapsed) && !clone(app.openDrawer);
    const { openDrawer } = app;
    const mode = collapsed === true ? "vertical" : "inline";
    const onMouseEnter = event => {
      if (openDrawer === false) {
        toggleOpenDrawer();
      }
      return;
    };
    const onMouseLeave = () => {
      if (openDrawer === true) {
        toggleOpenDrawer();
      }
      return;
    };
    const customizedTheme = themes[themeConfig.theme];
    const styling = {
      backgroundColor: customizedTheme.backgroundColor
    };
    const submenuStyle = {
      backgroundColor: "rgba(0,0,0,0.3)",
      color: customizedTheme.textColor
    };
    const submenuColor = {
      color: customizedTheme.textColor
    };
    return (
      <SidebarWrapper>
        <Sider
          trigger={null}
          collapsible={true}
          collapsed={collapsed}
          width="240"
          className="isomorphicSidebar"
          onMouseEnter={onMouseEnter}
          onMouseLeave={onMouseLeave}
          style={styling}
        >
          <Logo collapsed={collapsed} />
          <Scrollbars style={{ height: height - 70 }}>
            <Menu
              onClick={this.handleClick}
              theme="dark"
              className="isoDashboardMenu"
              mode={mode}
              openKeys={collapsed ? [] : app.openKeys}
              selectedKeys={app.current}
              onOpenChange={this.onOpenChange}
            >
              {options.map(singleOption =>
                this.getMenuItem({ submenuStyle, submenuColor, singleOption })
              )}
            </Menu>
          </Scrollbars>
        </Sider>
      </SidebarWrapper>
    );
  }
}

export default connect(
  state => ({
    app: state.App.toJS(),
    height: state.App.toJS().height
  }),
  { toggleOpenDrawer, changeOpenKeys, changeCurrent, toggleCollapsed }
)(Sidebar);

Yes, I know the code is too long but I think it makes sense to give a good context for the question to be answerable.

The react-adal packager from salvarovida uses under the hood the adal js library, so basically its a wrapper.

Aparently its possible to get roles with this line of code, but not sure how to use it and where.

https://github.com/AzureAD/azure-activedirectory-library-for-js/issues/713

like image 558
Luis Valencia Avatar asked May 29 '18 08:05

Luis Valencia


1 Answers

Since you have mentioned you will create Azure AD roles, you can add users/groups to these roles as well.

See Add app roles in azure ad apps.

Once you have mapped these roles to a user, you can query Graph API for user.

GET https://graph.microsoft.com/v1.0/me

This will return response as

HTTP/1.1 200 OK
Content-type: application/json
Content-length: 491
{

   "displayName": "displayName-value",
   "givenName": "givenName-value",
   "mail": "mail-value",
   "surname": "surname-value",
   "userPrincipalName": "userPrincipalName-value",
   "id": "id-value"
}

Now you can query Graph API for fetching roles associated with that user

POST /groups/{id}/getMemberGroups

The response will contain a list of all groups mapped to the user. You can store your newly created groups/roles which you created in web.config and check if they are a part of the returned list.

Note: You need permissions for Directory.Read.All, Directory.ReadWrite.All, Directory.AccessAsUser.All before you can make this call.

Source: https://docs.microsoft.com/en-us/graph/api/user-list-memberof?view=graph-rest-1.0

You can make these API calls using either Web API or JS. I prefer to stick to using WebAPI for fetching roles and returning these roles in an API call (to fetch menus, set permissions, etc.) on the front-end as well.

Also, please see https://graphexplorer.azurewebsites.net/ for testing these calls before implementing them in code.

If you wish for a sample app, see this - Authorization in a web app using Azure AD application roles & role claims

like image 199
Ashutosh Avatar answered Sep 24 '22 17:09

Ashutosh