0

I'm trying to implement App Roles in a single-tenant React + .NET Core app of ours. This app has successfully been authenticating users via MSAL, so this is just an incremental addition.

I have the app roles set up in Azure AD, and I have the Authorize attribute with role restrictions working in the .NET back-end, but for some reason I'm unable to get the roles via the react MSAL library, even though when I manually decode the token I see them in there.

I was referring to this MS sample for my code. In my index.js, I have the following:

export const msalInstance = new PublicClientApplication(msalConfig);

const accounts = msalInstance.getAllAccounts();

if (accounts.length > 0) {
  msalInstance.setActiveAccount(accounts[0]);
}

Then, in the test page I have, I'm trying to access the roles array in this way (just as a test to print them out):

const TestComponent = () => {
  const { instance } = useMsal();

  useEffect(() => {
    const activeAccount = instance.getActiveAccount();
    setTokenRoles(activeAccount?.idTokenClaims?.roles);
    // I've also tried:
    // setTokenRoles(activeAccount?.idTokenClaims['roles']);
  }, [instance]);

  return (
    <div>
      ROLES: {JSON.stringify(tokenRoles)}
    </div>
  );
};

Unfortunately, tokenRoles is null. When I inspect entire idTokenClaims object, I see all the other claims, but no roles. However, I do see them in the token itself:

{
  ...
  "roles": [
    "Packages.Manage"
  ],
  ...
}

I'm really hoping to avoid manually decoding the token. There has to be a way to get it out of MSAL.

Josh Anderson
  • 5,975
  • 2
  • 35
  • 48
  • If you believe there is a bug in MSAL.js here, please file a bug on Github. https://github.com/AzureAD/microsoft-authentication-library-for-js/issues – jasonnutter Jul 14 '22 at 23:06
  • Any debugging you can do in the library to help determine the root cause also appreciated, thanks! – jasonnutter Jul 14 '22 at 23:06
  • @josh-anderson sorry for being pedantic, but just to clarify, are you decoding the ID token or the access token? – derisen Jul 14 '22 at 23:40
  • I decoded the access token. What I get via the acquiretokensilent call. I assumed that’s the same basic content tied to the account, but perhaps I’m checking the wrong thing. – Josh Anderson Jul 15 '22 at 00:54
  • I’m not ready to say it’s a bug because I’m not sure I’m using the correct method to get the app roles the authenticated user has. – Josh Anderson Jul 15 '22 at 00:56
  • This morning I checked the access and account tokens. Neither JSON object returned by MSAL have the `roles` array. Manually decoding the `idToken` and `accessToken`, the roles are only encoded in the access token JWT. If MSAL doesn't natively decode and include in any objects, I guess I can manually decode it. Just doesn't feel like the best way to tackle it. – Josh Anderson Jul 15 '22 at 10:39
  • 1
    @JoshAnderson ok that explains. So if you enable/create app roles on your web API's app registration, they will appear on the access token that your client SPA app acquires. But if you want the roles claim in the ID token, you'll need to enable/create same app roles on your SPA app's app registration. Basically if you have a SPA + web API setup, you'll need to create app roles twice, one in each app's registration. The sample discusses this more [here](https://github.com/Azure-Samples/ms-identity-javascript-react-tutorial/tree/main/5-AccessControl/1-call-api-roles#registration) – derisen Jul 15 '22 at 16:35

2 Answers2

2

Jason Nutter's comments provided the answer, and in case this helps others I figured I'd give it a write-up.

Per the MS docs, I put the app roles on the back-end app registration. This is why I am able to have the Authorize(Roles = "Role") attribute work on the back-end. The reason I can see the roles in the access token is that the token is retrieved with the scope for that back-end API. But because I don't have those roles mirrored on the front-end app registration, I don't see anything in the id token.

There would be two options if you wanted to use the Azure app roles:

  1. Mirror the app roles in the front-end app registration. In this way you'd have access to the roles in the id token. This sounds not good because I could foresee a typo or mismatch causing weird issues. I'm sure there might be a way using the Azure API to have a process that would sync the roles, but that's not worth it in my opinion.
  2. Manually decode the access token on the front-end. The cleanest way I could think of to do this would be to create a roles context that would pull an access token, decode it, and store the roles for child components to refer to.

Another alternative would be to manage roles in the app itself. For us, the application in question is single-tenant, so there's not much need to do that. However, we do have a multitenant app we are moving to MSAL, and in that case we will already need to do things like validate that the tenant is authorized, and we will need more granular permissions than what this internal app needs, so we will likely have role system and have the front-end retrieve role and profile data from the back-end upon authentication through MSAL.

EDIT: What I ultimately did...
I did indeed keep the roles in the back-end only, then created a user context object that the front-end would retrieve. This user context includes the app roles, as well as other convenience data points like nickname, and is used by a React context and provider that I wrap my app in.

Josh Anderson
  • 5,975
  • 2
  • 35
  • 48
  • Good find on mirroring the app roles, but agree that could be error-prone. Is this approach documented by MS? – Joe Eng Dec 22 '22 at 00:27
  • Not explicitly as a solution to the problem. There are obviously docs on setting up the roles on apps, but nothing on coordination between two connected systems that I could find. I updated the answer with a bit about what we ultimately did — let the back end own the access piece and send the front end a user context object that tells is what the logged-in user can do. – Josh Anderson Dec 23 '22 at 11:33
0

Josh's answer helped me a lot, but I'm adding my solution as neither was ideal for me. I went a slightly different route. I didnt want to manually decode the token as the frontend needs the roles before calling an api (for conditionals in template etc.) and the msalService only retrieves the token with the roles when it implicitly calls an api with the scope which contain the roles i need. Mirroring the app roles isnt ideal either as its messy and bad practice IMO.

So instead I changed it so that I only have a single App Registration for both the Angular App and and API. Then I use the same clientId in both. Just remember to add the Single-Page-Application redirects in Manage -> Authentication.

This way when the Angular MSAL lib gets the scope for itself on login, it also gets the roles for the API. This is also a bit neater and makes it easier to split it out for other instances like Staging / UAT.

Meaning you make a seperate app registration for Staging with the same app roles & scope name. Then you can just swap the client Id's in both the API & FE and you have a entirely seperate instance of roles etc. This way you can modify a users app role on staging without it effecting their access on Prod. Very important for UAT, and for creating & modifiying Roles with prod still working normally.

Otherwise, with two app registrations for a single instance, its a bit messy to get working. (Also insanely messy if you went the mirroring of app roles route)