2

I am working on a single page application (SPA) app that grants access to specific paths in the application, based on roles setup in Azure AD for the user logging in. As per this https://github.com/Azure-Samples/ms-identity-javascript-react-tutorial/tree/main/5-AccessControl/1-call-api-roles

This is my authConfig file:

const clientId = window.REACT_APP_CLIENTID
export const msalConfig = {
    auth: {
        clientId: clientId,
        authority: window.REACT_APP_AUTHORITY,
        redirectUri: 'http://localhost:3000/todolist/', // You must register this URI on Azure Portal/App Registration. Defaults to window.location.origin
        postLogoutRedirectUri: "/", // Indicates the page to navigate after logout.
        navigateToLoginRequestUrl: false, // If "true", will navigate back to the original request location before processing the auth code response.
    },
    cache: {
        cacheLocation: "sessionStorage", // Configures cache location. "sessionStorage" is more secure, but "localStorage" gives you SSO between tabs.
        storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge
    },
    system: {
        loggerOptions: {
            loggerCallback: (level, message, containsPii) => {
                if (containsPii) {
                    return;
                }
                switch (level) {
                    case LogLevel.Error:
                        console.error(message);
                        return;
                    case LogLevel.Info:
                        console.info(message);
                        return;
                    case LogLevel.Verbose:
                        console.debug(message);
                        return;
                    case LogLevel.Warning:
                        console.warn(message);
                        return;
                }
            }
        }
    }
};

/**
 * Add here the endpoints and scopes when obtaining an access token for protected web APIs. For more information, see:
 * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/resources-and-scopes.md
 */
export const protectedResources = {
    apiTodoList: {
        todoListEndpoint: window.REACT_APP_APIENDPOINT+"/api/v2/support/list",
        scopes: [window.REACT_APP_APIENDPOINT+"/access_as_user"],
    },
}

/**
 * Scopes you add here will be prompted for user consent during sign-in.
 * By default, MSAL.js will add OIDC scopes (openid, profile, email) to any login request.
 * For more information about OIDC scopes, visit: 
 * https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes
 */
export const loginRequest = {
    scopes: [...protectedResources.apiTodoList.scopes]
};

export const appRoles = {
    TaskUser: "TaskUser",
    TaskAdmin: "TaskAdmin",
    TrialAdmin: "Trial.Admin",
    GlobalAdmin: "Global.Admin"
}

You can see the authorization parameters (clientId, authority etc..) under 'auth'

Here is the App.jsx file (I believe there needs to be some change made here). You can see 'RouteGuard' that renders the Component {TodoList}, when the path 'todolist' is accessed.

import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import { MsalProvider } from "@azure/msal-react";

import { RouteGuard } from './components/RouteGuard';
import { PageLayout } from "./components/PageLayout";
import { TodoList } from "./pages/TodoList";


import { appRoles } from "./authConfig";


import "./styles/App.css";


const Pages = () => {
  return (
    <Switch>
      <RouteGuard
        exact
        path='/todolist/'
        roles={[appRoles.TaskUser, appRoles.TaskAdmin, appRoles.TrialAdmin, appRoles.GlobalAdmin]}
        Component={TodoList}
      />
    </Switch>
  )
}

/**
 * msal-react is built on the React context API and all parts of your app that require authentication must be 
 * wrapped in the MsalProvider component. You will first need to initialize an instance of PublicClientApplication 
 * then pass this to MsalProvider as a prop. All components underneath MsalProvider will have access to the 
 * PublicClientApplication instance via context as well as all hooks and components provided by msal-react. For more, visit:
 * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-react/docs/getting-started.md
 */
const App = ({ instance }) => {

  return (
    <Router>
      <MsalProvider instance={instance}>
        <PageLayout>
          <Pages instance={instance} />
        </PageLayout>
      </MsalProvider>
    </Router>
  );
}

export default App;

And the 'Routeguard' component,

import React, { useState, useEffect } from "react"; import { Route } from "react-router-dom"; import { useMsal } from "@azure/msal-react";

export const RouteGuard = ({ Component, ...props }) => {

const { instance } = useMsal();
const [isAuthorized, setIsAuthorized] = useState(false);

const onLoad = async () => {
    const currentAccount = instance.getActiveAccount();

    if (currentAccount && currentAccount.idTokenClaims['roles']) {
        let intersection = props.roles
            .filter(role => currentAccount.idTokenClaims['roles'].includes(role));

        if (intersection.length > 0) {
            setIsAuthorized(true);
        }
    }
}

useEffect(() => {
    onLoad();
}, [instance]);

return (
    <>
        
        {
            isAuthorized
                ?
                <Route {...props} render={routeProps => <Component {...routeProps} />} />
                :
                <div className="data-area-div">
                    <h3>You are unauthorized to view this content.</h3>
                </div>
        }
    </>
); };

The issue I am facing here is in the authConfig file, the cacheLocation is 'LocalStorage' and I can see information stored there, like the clientId, authToken, realm, homeAccountId etc. I would want to control what is shown here, I can see important info shown here and I dont want to show it, while storage is still Local.

Any suggestions on how this can be done ? I dont want to use 'sessionStorage' - Thanks !

Skadoosh
  • 699
  • 2
  • 11
  • 27

1 Answers1

2

That information is required for (silent) future requests and as such can't be hidden away. Even if using Session Storage, the data is available if the session is active.

The only option is to use Memory Storage which requires login after every refresh, so user experience will take a hit. Note that even with this, a crafty tech user could still get the token as tricky as it might be if they had to.

Every authentication library is built in a similar way, with the assumption that the client (the browser) is secure. While the same user accessing this information is not really a huge problem on its own, if compromised, a hacker could make requests on behalf of the user.

This is why tokens expire with short lives and in case of a hack, the refresh token will have to be revoked to ensure the hacker doesn't use them. Not to mention, the affected device would have to be secured as well.

That being said, one approach would be to use a custom authentication proxy of sorts that fetches tokens on behalf of the user, secures them in the backend, and sets a http only cookie with a custom value that it can use to retrieve the token on subsequent requests. This adds complexity and latency on the backend, which is one of the things that JWT Tokens solve for.

PramodValavala
  • 6,026
  • 1
  • 11
  • 30