/react-azure-ad-starter

A Starter project for using Azure AD to protect React Components and Routes

Primary LanguageTypeScript

React + Azure AD Starter

This start app integrates the following features:

Why this starter?

  • The awesome @azure/msal-react and @microsoft/mgt-react libraries don't well play together without some trickery
  • Private and protected routes and components should just work (without lots of boiler plate code!)
  • Using user access tokens should be easy (without lots of boiler plate code!)

Getting started:

  1. Create an Azure AD app registration, and collect the Client ID, Tenant ID, and Scop in a .env file at the root of his repo, like so:

    REACT_APP_CLIENT_ID={{Azure AD app client ID}}
    REACT_APP_AUTHORITY=https://login.microsoftonline.com/{{Azure AD tenant ID}}
    REACT_APP_SCOPE={{ Scope URI }}
  2. Install the dependencies with yarn and start the app with yarn start, and view it at localhost:3000, as usual.

  3. View the examples:

  • Forced user login src/App.tsx
  • Private and protected routing in src/main.tsx
  • Private and protected components in src/components/page-1/index.tsx
  • Using user access tokens in src/components/page-1/index.tsx

Using User Access Tokens

User Access Tokens can be obtained in the app using the fetchUserAccessToken function , typically for fetching protected content from a protected API

const [protectedAPIContent, setProtectedAPIContent] = useState();

useEffect(async () => {
  const userAccessToken = await fetchUserAccessToken();
  const response = await fetch("/some/protected/route.json", {
    headers: {
      Authorization: `Bearer ${userAccessToken}`,
    },
  });

  // do things with the protected contents
  setProtectedAPIContent(await response.json());
});

Restricting Content to using User Authentication

Manual control

To conditionally render components manually, the useUserAccount() hook provides access to the user account and the the users assigned roles.

import { useUserAccount } from "src/auth/UserAccount";

const MyAwesomeComponent = () => {
  const AccountInfo = useUserAccount();
  return (
    <span>
      You are{" "}
      {"admin" in (AccountInfo.roles || []) ? "Authorized" : "Unauthorized"}
    </span>
  );
};

However, the following components will typically offer a more thorough Auth/Auth controlled rendering.

Private Content

The PrivateComponent requires that a user is signed in in order to view the private contents.

<PrivateComponent
  unauthenticated={() => <p>Loading...</p>}
  loading={() => <p>Loading...</p>}
  content={() => <div>You must be logged in to view this component</div>}
/>

Protected Content

The RBACProtectedComponent requires that a user is signed in and is assigned a role, as follows

  • When the requiredRole attribute is a string, the user is required to be assigned a role in order to view the protected content:

    <RBACProtectedComponent
      requiredRole="RequiredRole" // string
      loading={() => <p>Loading...</p>}
      unauthorized={() => <p>UNAUTHORIZED</p>}
      unauthenticated={() => <p>LOGGED OUT</p>}
      content={() => (
        <p>You must be assigned the "RequiredRole" to view this component</p>
      )}
    />
  • When the requiredRole attribute is an array of strings, the user is required to be assigned have all of provided roles in order to view the protected content:

    <RBACProtectedComponent
      requiredRoles={["RoleOne", "RoleTwo"]} // string[]
      loading={() => <p>Loading...</p>}
      unauthorized={() => <p>UNAUTHORIZED</p>}
      unauthenticated={() => <p>LOGGED OUT</p>}
      content={() => (
        <p>
          You must be assigned <em>both</em> "RoleTwo" and "RoleTwo" to view this
          component
        </p>
      )}
    />
  • When the allowedRoles is provided, the user is required to have at least one of the provided roles in order to view the protected content:

    <RBACProtectedComponent
      allowedRoles={["RoleOne", "RoleTwo"]} // string[]
      loading={() => <p>Loading...</p>}
      unauthorized={() => <p>UNAUTHORIZED</p>}
      unauthenticated={() => <p>LOGGED OUT</p>}
      content={() => (
        <p>
          You must be assigned <em>either</em> "RoleTwo" and "RoleTwo" to view
          this component
        </p>
      )}
    />

Note that a simple way to enforce protected routing is to use () => <Navigate to="/unauthorized" replace /> in either the unauthorized or unauthenticated properties of an RBACProtectedComponent