Azure AD auth - calling web api (multi-tenant) with organizations token fails

897 Views Asked by At

I have an ASP.NET Core web app that calls an ASP.NET Core web api and I'm using Azure AD with Microsoft.Identity.Web . Both have different app registrations in Azure AD and both are configured correctly (Both app registrations now each other, the web app also requests access to the web api, etc..)

This is a multi-tenant application. The web app (front-end) can be used by multiple tenants. Only the web app calls the web-api (back-end), but it also needs to support multiple tenants because we have a database for each tenant.

I feel like I want to use the organizations approach and not the common approach because this application should only be used by other businesses. People with personal accounts should not be able to log in, which is (as far as I know) what common is for.

Funnily enough, when I try to log in using my personal account, I get an error about it not being allowed. Probably because of how I configured my Azure AD app registration?

Web app configuration

The web app is configured like this:

services
    .AddMicrosoftIdentityWebAppAuthentication(Configuration)
    .EnableTokenAcquisitionToCallDownstreamApi(Configuration.GetValue<string>("Scopes").Split(" "))
    .AddDistributedTokenCaches();

----
Configuration:

  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "organizations",
    "ClientId": "MY_CLIENT_ID",
    "CallbackPath": "/signin-oidc",
    "SignedOutCallbackPath ": "/signout-callback-oidc",
    "ClientCertificates": [
      {
        "SourceType": "KeyVault",
        "KeyVaultUrl": "KEYVAULT_URL",
        "KeyVaultCertificateName": "AzureActiveDirectoryAppCert"
      }
    ]
  },

  "Scopes": "https://companyname.com/webapi/Api.Access",

When I call my API, I get a token like this and then call the API:

var tokenAcquisition = context.HttpContext.RequestServices.GetRequiredService<ITokenAcquisition>();

var accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(scopes);

// Token is sent successfully to the API

Web api configuration

The web api is configured like this:

 services
    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Audience = "https://companyname.com/webapi;
        options.Authority = "https://login.microsoftonline.com/organizations";
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = false // TODO: Validate that only allowed tenants can access API
        };
    });

The problem

This entire setup works PERFECTLY when I use common, but when I use organizations I get the following error:

 System.InvalidOperationException: IDX20803: Unable to obtain configuration from: 'https://login.microsoftonline.com/organizations/.well-known/openid-configuration'.

---> System.IO.IOException: IDX20807: Unable to retrieve document from: 'https://login.microsoftonline.com/organizations/.well-known/openid-configuration'. HttpResponseMessage: 'StatusCode: 400, ReasonPhrase: 'Bad Request', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent

Some more error details: {"error":"invalid_tenant","error_description":"AADSTS90002: Tenant 'organizations' not found. Check to make sure you have the correct tenant ID and are signing into the correct cloud..."}

I know that the common endpoint has valid openid-configuration whereas the organizations endpoint returns a 400, but I don't understand how I am then supposed to make this work.

I tried following an official sample (which uses services.AddMicrosoftIdentityWebApiAuthentication(Configuration)) but this also didn't work for the same reasons. Another SO post also looked promising but caused the same error.

How can I add multi-tenant support (whilst still being able to restrict it to specific tenants) in an ASP.NET Core web api that uses an Azure AD App registration?

1

There are 1 best solutions below

2
kavyaS On

When I used the organizations endpoint , I was getting the same error.

appsetting.json

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "testthetenant365.onmicrosoft.com",
    "ClientId": "50xxxx06e",
    "TenantId": "Organizations",
    "ClientSecret": "ZHxxxbGh",
    //Audience": "50xxx06e",
    "CallbackPath": "/signin-oidc"
  },
  "DownstreamApi": {
    /*
     'Scopes' contains space separated scopes of the Web API you want to call. This can be:
      - a scope for a V2 application (for instance api:xx/access_as_user)
      - a scope corresponding to a V1 application (for instance <App ID URI>/.default, where  <App ID URI> is the
        App ID URI of a legacy v1 Web application
        App ID URI of a legacy v1 Web application
      Applications are registered in the https:portal.azure.com portal.
    */
    "BaseUrl": "https://graph.microsoft.com/v1.0",
    //"Scopes": "https://graph.microsoft.com/User.Read"
    //"Scopes": "api://xxxx/access_as_user"
     "Scopes": "https://graph.microsoft.com/.default"

  },

Error:

System.InvalidOperationException: IDX20803: Unable to obtain configuration from: 'https://login.microsoftonline.com/Organizations/xxx/v2.0/.well-known/openid-configuration'.
 ---> System.IO.IOException: IDX20807: Unable to retrieve document from: 'https://login.microsoftonline.com/Organizations/xxx/v2.0/.well-known/openid-configuration'. HttpResponseMessage: 'StatusCode: 404, ReasonPhrase: 'Not Found', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:

enter image description here

Note: /common endpoints supports personal accounts but /organizatioms support only work or school accounts.

Then I checked and changed the support account type of my registered application:

enter image description here

Then it asked for admin consent to access the endpoint. Note: The account with which I logged in was work and school account

enter image description here

I have "accessTokenAcceptedVersion": 2, in the manifest of my App registration.

Make sure the tenant with user is going to login is allowed by the application and granted admin consent to api permissions.z

And check on the audience part Client application configuration (MSAL) - Microsoft Entra | Microsoft Learn

With permissions granted and with above configuration, the endpoint worked:

enter image description here

And scope is for graph as I am calling grapph endpoint with /.default . scope: https://graph.microsoft.com/.default.

For your api : check scope if it has to be granted for other permissions api://<clientId>/.default.

enter image description here

Alternatively , if you know the tenant Id’s of tenants to be allowed , they can be validated. Startup.cs

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Authority = "https://login.microsoftonline.com/common";
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidIssuers = new[] { "https://login.microsoftonline.com/TENANT_ID1/v2.0", "https://login.microsoftonline.com/TENANT_ID2/v2.0" },
            //token validation parameters...
        };
    });

See Convert single-tenant app to multi-tenant on Azure AD - Microsoft Entra | Microsoft Learn