Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why Azure AD fails to login non-admins in multi-tenant scenario?

Environment:

Two Azure ADs: Company, Customers

Company publishes an ASP.NET5 web app called Portal, the app is setup to be multi-tenant.

Customers have 2 user: user (who is just a user) and admin (who is a Global Administrator in the directory).

Portal, is initially set up to ask for 1 Application Permission: Read Directory Data

-

Here comes the flow that I went through, and I believe Azure AD misbehaves at multiple steps. Please point out if I am missing something.

  1. I open the web app, and first try to sign in as admin
  2. I have to consent to the Read Directory data permission, so I do that
  3. Application appears (I have no roles assigned yet, which is fine) -- so far everything works.
  4. I re-open the web-app in a new incognito session, and try to sign in as the user
  5. Now, I get [AADSTS90093: This operation can only be performed by an administrator. Sign out and sign in as an administrator or contact one of your organization's administrators.] -- the admin already consented, so why do I get this??
  6. I go to Company AD and change the application permissions to include Read & Write Directory data
  7. I go to Customer AD check the app Portal and the dashboard already shows the new permission listed. No one had to consent! The admin do not see any change even on next login. How is this not a security hole?

My understanding of https://msdn.microsoft.com/en-us/library/azure/dn132599.aspx is that Application Permissions are not deprecated.

UPDATE

My configuration in the WebApp:

app.UseOpenIdConnectAuthentication(options =>
{
   options.ClientId = Configuration.Get("ActiveDirectory:ClientId");
   options.Authority = String.Format(Configuration.Get("ActiveDirectory:AadInstance"), "common/");   //"AadInstance": "https://login.windows.net/{0}"
   options.PostLogoutRedirectUri = Configuration.Get("ActiveDirectory:PostLogoutRedirectUri");    //"PostLogoutRedirectUri": "https://localhost:44300/"

   options.TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
   {
       // The following commented-out line should work according to
       // http://stackoverflow.com/questions/29317910/why-does-the-role-claim-have-incorrect-type
       // But, it does not work in ASP.NET5 (currently), so see the "Hack." down below
       // RoleClaimType = "roles",

       ValidIssuers = new[] { "https://sts.windows.net/a1028d9b-bd77-4544-8127-d3d42b9baebb/", "https://sts.windows.net/47b68455-a2e6-4114-90d6-df89d8468abc/" }
   };

   options.Notifications = new OpenIdConnectAuthenticationNotifications
   {
       RedirectToIdentityProvider = (context) =>
       {
           // This ensures that the address used for sign in and sign out is picked up dynamically from the request,
           // which is neccessary if we want to deploy the app to different URLs (eg. localhost/immerciti-dev, immerciti.azurewebsites.net/www.immerciti.com)
           string appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase;
           context.ProtocolMessage.RedirectUri = appBaseUrl;
           context.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl;
           return Task.FromResult(0);
       },

       AuthorizationCodeReceived = async context =>
       {
           // Get Access Token for User's Directory
           try
           {
               var identity = (ClaimsIdentity)context.AuthenticationTicket.Principal.Identity;

               // Hack. TODO: keep an eye on developments around here
               foreach (var claim in identity.FindAll("roles"))
               {
                   // Readd each role with the proper claim type
                   identity.AddClaim(new Claim(identity.RoleClaimType, claim.Value, claim.ValueType, claim.Issuer, claim.OriginalIssuer));
               }
           }
           catch (AdalException)
           {
               context.HandleResponse();
               context.Response.Redirect("/Error/ShowError?errorMessage=Were having trouble signing you in&signIn=true");
           }
       }
   }; 
};
like image 927
Gabor Avatar asked Apr 22 '15 08:04

Gabor


People also ask

Can Azure Active Directory have multiple tenants?

In the Azure portal, you can configure your app to be single-tenant or multi-tenant by setting the audience as follows. All user and guest accounts in your directory can use your application or API. Use this option if your target audience is internal to your organization.

How many tenants can a single user belong to in Azure Active Directory?

A single user can belong to a maximum of 500 Azure AD tenants as a member or a guest.

Can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant?

This can happen if the application with identifier X has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have mis configured the Identifier value for the application or sent your authentication request to the wrong tenant.

Can they associate multiple Azure subscriptions to the same Azure Active Directory tenant?

An Azure subscription has a trust relationship with Azure Active Directory (Azure AD). A subscription trusts Azure AD to authenticate users, services, and devices. Multiple subscriptions can trust the same Azure AD directory. Each subscription can only trust a single directory.


1 Answers

Thanks for the information you've provided. I'm going to answer #7 first, because it looks pretty alarming. It does at first glance look like a security hole, but it's not. It's a bug in the Azure Management Portal that we are working to fix. In the "customers" tenant view, the UX is showing the permissions that the application (defined in the company tenant) is requesting. It should be showing the permissions actually granted in the "customers" tenant. In this case, if your app actually tries a call to write to the Graph API it'll get an access denied error. Anyways - not a security hole - but can sure understand why it looked that way to you - so sorry about this. We'll try to get this fixed as soon as we can.

On to some of your other questions about consent behavior... BTW this is something we are looking to improve in our documentation. Anyways, I'll try and answer this broadly in terms of the design behavior, because it looks like you've changed your app config multiple times.

If you pick any app permissions (not delegated permissions), the consent UX defaults to the "consent on behalf of the organization" experience. In this mode the consent page ALWAYS shows, whether the admin consented previously or not. You can also force this behavior if you make a request to the authorize endpoint with the QS parameter of prompt=admin_consent. So let's say you went down this path AND the only permission you have is app-only "Read Directory" and the admin consents. Now a user comes the user doesn't have any grant that allows them to sign in and get an id_token for the app (Read Directory app-only is not currently good for this), so the consent dialog tries to show the admin on behalf of org consent, but this is a non-admin so you get the error. Now, if you add the delegated "sign me in and read my profile" permission for the app, and have your admin reconsent, you'll see that now the user will not be prompted for consent. What I'll do is go back to our team and see whether ANY directory permission (app only or delegated) should allow any user to get a sign in token. One could argue that this should be the case.

HTHs,

like image 179
Dan Kershaw - MSFT Avatar answered Oct 27 '22 06:10

Dan Kershaw - MSFT



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!