8
votes

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");
           }
       }
   }; 
};
1
Hi Gabor. Thanks for reporting this. Can you tell us a little more about your application? Is it based on any of the sample apps? Is your app using an OAuth or OpenID Connect flow to prompt for authorization/consent, or are you using WS-Fed or SAML authentication flow, and that's prompting for consent? We are trying to reproduce the issues you raised, and more information about your app (including things like fiddler traces) will really help. Thanks.Dan Kershaw - MSFT
Hi Dan, I mainly relied on 3 of your sample apps: WebApp-OpenIdConnect-AspNet5 (to see how to configure stuff on ASP.NET5), WebApp-MultiTenant-OpenIdConnect-DotNet (to see the multi-tenant specifics) and WebApp-RoleClaims-DotNet (to set up the app manifest in Azure AD and take care of authorization in my webapp). So it is using the OpenID flow. Added some more info to the question.Gabor
The traces have everything decoded...hopefully not too much actually, but as far as I could see these are only dummy data at the moment.Gabor
There is a significant difference compared to my experience yesterday: this time the Admin had to re-consent on each login (whether the access rights changed or not) -- there is a trace on this too.Gabor
The correct link for the Fiddler traces: 1drv.ms/1aUwTY4Gabor

1 Answers

4
votes

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,