Home > other >  Successfully authenticated B2B user can't query Microsoft Graph API
Successfully authenticated B2B user can't query Microsoft Graph API

Time:07-14

I have an AAD registered enterprise application set up as single tenant, allowing external guest users to log in. It's set to use ID tokens only (not access tokens) and public client flows are disabled.

The app registration is set to use delegated API permissions for User.Read, and the consent flow is engaged when an external user accepts their invitation after user setup. The app never tries to query graph api for anything other than the user's own profile information.

The resource tenant is using default settings in AAD for "External identities". Guest user access restrictions are set to allow guest users limited access to properties and membership of directory objects.

When a member user in the resource tenant logs in, they can query the graph api successfully retrieving their personal profile details.

When a guest user (B2B) from another AAD tenant logs in, they can successfully log in and view a page in the web app, but when they try to make a call to get their own profile information, graph.microsoft.com is returning a 403 error. The exception is

ServiceException: Code: Authorization_RequestDenied Message: Insufficient privileges to complete the operation..

I've come at this by using the "Me" call and trying to grab the specific user with the id correlating to the oid in the token and the "ext" syntax of the guest UPN. Either attempt generates the above exception, but examples of the calls are below:

await graphClient.Me.Request().Select(u => new { u.DisplayName, u.JobTitle, u.OfficeLocation, u.City, u.CompanyName }).GetAsync();
await graphClient.Users[{oid}].Request().GetAsync();

I've looked at the tokens for a member user and a guest user in jwt.ms. I can see that in the guest user's token, the proper scope for User.Read is there. Additionally, the tenant id is the resource tenant, and the oid in the token is the guid for the guest user in the resource tenant. The appid is correct in the token as well.

So external guests are able to log in to the app just fine, are shipping out a token to graph, but just getting a 403 for the trouble. I did notice that in the token, the upn and email fields are using the guest user's email address rather than the real UPN on the resource tenant ([userEmail]#ext#@myDomain.com), but I think I read that it doesn't matter as the token contains the correct oid (for external user in the resource tenant) and that's what's authoritative.

Any help or thoughts would be appreciated. More than happy to answer questions.

EDIT: I’ll add that for the API permissions, admin consent was granted on the resource tenant on behalf of the org.

CodePudding user response:

As your application is for single-tenant, users from different tenant/domain (external users) may not have API permissions as they are intended for only that tenant users.

To resolve the 403 error, please try the following:

  • Try to change your supported account type from single-tenant to multi-tenant like below:

Go to Azure Portal -> Azure Active Directory -> App registrations -> Your App -> Authentication -> Save

enter image description here

If still the error persists, try changing the External identities setting like below:

Go to Azure Active Directory -> External identities -> External Collaboration settings -> Save

enter image description here

Try adding profile API permission like below:

enter image description here

CodePudding user response:

I've solved the problem, so thought I'd provide an answer for anybody else that comes across this.

Basically, a B2B external user is only able to query the Microsoft Graph API about itself if I add the User.ReadBasic.All delegated permission to the registered application and grant it admin consent. With this configuration, either of the following calls will return information:

await graphClient.Me.Request().Select(u => new { u.DisplayName, u.JobTitle, u.OfficeLocation, u.City, u.CompanyName }).GetAsync();
await graphClient.Users[{guest oid from resource tenant}].Request().Select(u => new { u.DisplayName, u.JobTitle, u.OfficeLocation, u.City, u.CompanyName }).GetAsync();
await graphClient.Users[{guest UPN from resource tenant}].Request().Select(u => new { u.DisplayName, u.JobTitle, u.OfficeLocation, u.City, u.CompanyName }).GetAsync();

If I downgrade this api permission (User.ReadBasic.All) to just User.Read in the application configuration, it will reliably reproduce the 401 response and exception mentioned in the question.

Even if the scope in the application variable for setting up the graph client is left as User.Read, the token going to graph.microsoft.com will include ReadAll.Basic in its scope.

User.ReadBasic.All was the tightest (least privileged) configuration I could set and be successful with the B2B user querying information about itself.

Even though User.ReadBasic.All says it doesn't require admin consent, the query would not work without it.

Thinking this may be related to having the wrong unique_name in the token going to graph for an external user, or something similar, I had also tried altering the registered application's token by trying to explicitly include the UPN value, but per MS this does not alter the token that gets sent to Microsoft Graph. True enough, no matter what I seemed to do for configuring optional claims in the application token, the token being sent to Graph would never include a UPN value or change the unique_name or email values to use the guest UPN from the resource tenant.

So why this is required is unknown to me. Obviously Read All gives the ability to look up info on users other than the authenticated identity, so this seems less than ideal, but for whatever reason User.Read doesn't work with B2B users. I figure this must be related to the home tenant UPN being passed to graph rather than the resource tenant UPN. That despite Microsoft telling us not to use UPN and email for authorization, Graph must be doing so in this case. The token going to graph contains the proper tenant ID and user object ID for resource guest account. Is this a bug? Not sure.. But this answer can be considered a workaround at least.

  • Related