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

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.
I open the web app, and first try to sign in as admin
I have to consent to the Read Directory data permission, so I do that
Application appears (I have no roles assigned yet, which is fine) -- so far everything works.
I re-open the web-app in a new incognito session, and try to sign in as the user
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??
I go to Company AD and change the application permissions to include Read & Write Directory data
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");
}
}
};
};

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,

Related

"AADSTS65001: The user or administrator has not consented to use the application" but the Admin has consented

I am adapting the project sample provided by Microsoft for Multi-tenant Azure AD apps.
I am extending SurveyAuthenticationEvents.TokenValidated() so that in the sign up logic, I hit up Microsoft Graph to get the tenant display name so we can store something a little more meaningful than just a GUID to identify the new tenant.
Something like this:
Organization? org = default;
var tokenAcquisition = context.HttpContext.RequestServices.GetRequiredService<ITokenAcquisition>();
var auth = await tokenAcquisition.GetAuthenticationResultForUserAsync(new string[] { "User.Read" }, tenantId: azureTenantId, user: context.Principal); // "User.Read", "Organization.Read.All"
var graphClient = new GraphServiceClient(
new DelegateAuthenticationProvider(
(requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", auth.AccessToken); // context.SecurityToken.RawData);
return Task.CompletedTask;
}));
var results = await graphClient.Organization.Request().Select(x =>x.DisplayName).GetAsync();
org = results.FirstOrDefault();
The third line is failing with the exception:
Microsoft.Identity.Client.MsalUiRequiredException: AADSTS65001: The user or administrator has not consented to use the application with ID 'xxxxxx' named 'xxxxx'. Send an interactive authorization request for this user and resource.
Please note that this is IMMEDIATELY after the tenant administrator has just consented.
However, the error seems to be intermittent. In fact if I debug and break on the problematic line, wait a couple of seconds and then let it run it works fine. It is as if Azure AD needs a second or two after the consent to properly update the service principals and consent status for the new tenant, before it will issue an access token for a downstream API.
Am I missing something here or do I just add some retries and work around the issue?
If an admin consent is already believed to be done , maybe all of the required permissions listed in the sign-in request were not consented to
or
the wrong application was used based on the App-Id}from the table above.
In that case try to add this as an authorized client application
Once the application has been consented ,please make sure the prompt parameter is not being specified. If prompt parameter is still passed after consent this error might occur
For workaround delete the package , permissions and again add it so your permission request gets created again.Also check if you need additional permissions like openid , profile ,offline_access for refresh token in your case.
Please check other possible causes here
Troubleshooting consent in Azure AD | Azure Active Directory Developer Support Team (aaddevsup.xyz) which can guide to troubleshoot
Reference:
Unexpected consent prompt when signing in to an application - Microsoft Entra | Microsoft Docs
Based on some feedback on github (https://github.com/mspnp/multitenant-saas-guidance/issues/127) it appears that the issue is indeed due to timing issues with AzureAD infrastructure, possibly related to caching.
Would be fantastic if this was documented!
I have now introduced some retry logic that simply waits a few seconds and retries the same request (up to 5 times) and the sign up logic now works as expected.
Thanks to #dmcsweeney on github

Setting up an Application with Azure for use with Graph API outlook calendars

I'm aware that Graph API has a nice nuget package and I am confident on the code side of things, but my understanding is that I need to have the application set up in Azure and while there is a lot of documentation about this in general, I find it quite dense and I'm not confident I have the specifics down for how I need to set this portion up.
What I need my application to do is access an outlook calendar of a specific user that I own, read, search, add, delete and update calendar items. The integration assistant seems to suggest I need to configure a URI redirect and configure api permission. The default persmission is User.Read on graph API and if I try to add a permission, office 365 management seems like it might be the one I need except it specifically says just retrieving user information and nothing mentions outlook anywhere.
I need to know more or less the minimum steps in setting up the application in Azure to write a C# application that can make changes to outlook for a user.
need my application to do is access an outlook calendar of a specific user
Does it mean you need your app to have the abiltity to modify the callendar of any user you owned? If not, then it means you need your application to provide a sign in module and let users sign in, then the users can get authentication to call graph api and manage their own calendar, since this scenario, users give the delegate api permission, so they can't manage other users' calendar, so I don't think this is what you want.
If so, then you should use client credential flow to generate access token to call graph api. I think you know that when you want to call graph api, you have to get an access token which has correct permission first.
Ok, then let's come to the api permission, when you go to api document of the Calendar. You will see permissions like screenshot below:
Application permission type is suitable for client credential flow. And after viewing all the apis, you will find that they all need Calendars.ReadWrite except those not supporting Application type.
Then let's go to azure portal and reach Azure Active Directory. You need to create an Azure ad application and give the app Calendars.ReadWrite permission, then give the Admin consent.
Then you also need to create a client secret, pls go to Certificates & Secrets and add a new client secret, don't forget to copy the secret after you create it.
Now you've done all the steps. No need to set a redirect url, because you don't need to let the user to sign in your application. Let's see client credential flow document, it only require client_id, client_secret to generate access token.
Or in the code, you may use SDK like this :
using Azure.Identity;
using Microsoft.Graph;
public async Task<IActionResult> Index()
{
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "your_tenant_name.onmicrosoft.com";
var clientId = "azure_ad_app_id";
var clientSecret = "client_secret";
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
var calendar = new Calendar{ Name = "Volunteer" };
var events = await graphClient.Users["user_id_which_is_needed_to_list_calendar_events"].Events.Request()
.Header("Prefer","outlook.timezone=\"Pacific Standard Time\"")
.Select("subject,body,bodyPreview,organizer,attendees,start,end,location")
.GetAsync();
return View();
}

Azure Authentication .net core user consent

Situation
Adding additional scopes for API's in App registration not triggering user consent screen automatically.
When user has previously consented they are not redirected to the consent screen again.
I can see in various web posts references to options.Prompt = "consent" but not sure how to add this with conditional logic in the startup file.
How do I detect if there are new scopes added if the application is enhanced and they are added to the app registration and only add the options prompt redirect to the consent screen in the case of new scopes, not on every login?
.AddMicrosoftIdentityWebApp(options => {
Configuration.Bind("AzureAd", options);
options.Prompt = "select_account";
if (**how to detect if there are new scopes**)
{
options.Prompt = "consent";
}
options.Events.OnTokenValidated = async context => {
var tokenAcquisition = context.HttpContext.RequestServices
.GetRequiredService<ITokenAcquisition>();
var graphClient = new GraphServiceClient(
new DelegateAuthenticationProvider(async (request) => {
var token = await tokenAcquisition
.GetAccessTokenForUserAsync(GraphConstants.Scopes, user: context.Principal);
request.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", token);
})
);
When the user consent the permissions, essentially the permissions are granted to the service principal(i.e. enterprise application) corresponded to the AD App that the user logged in, navigate to the enterprise application corresponded to the AD App in the portal -> Permissions, you will find the permissions that the user consented.
So when the same user login the app again, it will not promote the user to consent the same permissions again by default, because the permissions have already granted to the service principal, just if there are new permissions added to the AD App -> API permissions, then it will promote the user to consent the new permissions again.
For your requirement, if you want the user to be aware of what permissions the application was asking for, just consent for one time is enough, I don't think you need to let him consent again and again. Also no need to detect if there are new scopes added when using options.Prompt = "consent", because it will let the user consent the new permissions automatically. In conclusion, I don't think you need to do anything additional, the default behavior by azure is enough.

How to get a list of app registrations in Azure tenant

I need to get a list of app registrations in my Azure tenant.
I am using the tutorial found here.
Also, I granted permission to my app per this document.
I modified the sample app code as follows:
// Constants.cs
public const string ApplicationReadAll = "Application.Read.All";
Startup.cs:
// Added permission to read all applications
services.AddWebAppCallsProtectedWebApi(Configuration, new string[] { Constants.ScopeUserRead, Constants.CalendarsReadWrite, Constants.ApplicationReadAll })
.AddInMemoryTokenCaches();
HomeController.cs:
// Get list of applications.
Graph::GraphServiceClient graphClient = GetGraphServiceClient(new[] { Constants.ApplicationReadAll });
try
{
var me = await graphClient.Applications.Request().GetAsync();
ViewData["Me"] = me;
}
I have five apps registered in my tenant. The response returns zero, as it does when I make the call from Graph Explorer. I am logging in as Global Admin.
See also this question.
Here is what I discovered:
Directory.Read.All permission is required. Application.Read.All doesn't cut it.
The application (ClientID) that you use must have it's Supported account type set to "My organization only".
If necessary you can change this in the Manifest as follows:
"signInAudience": "AzureADMyOrg",
Full credit and many thanks to Mark Foppen. Read his excellent blog post and see his code on github.

Azure Active Directory Authentication and SharePoint CSOM

I have a web application, which connects to SharePoint (customer tenant) to create sites & various List.
To access the customers Sharepoint environment, I used OpenIdConnectAuthenticationOptions which prompts user for AAD creditinals and then the various options for which user wants to provide access ( This App and various API access needed is configured in AAD - when allowing access to "O365 SharePoint Online"
Using OpenIdConnectAutheticationOption, on AuthorizationCode Received the code is used to get "AccessToken".
Using this "AccessToken" to get clientContext gives error:
"401 - Not authorized"
How one can get the required token which allows CSOM operation?
The code used from -
Active Directory Dot net Webapp Multitenant
In the controller OnboardingController, Processcode function, after getting AcquireTokenByAuthorizationCodeAsync following code is used -
string siteUrl = "https://svtestsite.sharepoint.com/sites/powerapps";
ClientContext ctx = new ClientContext(siteUrl);
ctx.ExecutingWebRequest +=
delegate(object oSender, WebRequestEventArgs webRequestEventArgs)
{
webRequestEventArgs.WebRequestExecutor.RequestHeaders["Authorization"] =
"Bearer " + result.AccessToken; // accessToken;
};
ctx.Load(ctx.Web, p => p.Title);
ctx.ExecuteQuery();
Console.WriteLine(siteUrl);
Console.WriteLine(ctx.Web.Title);
The code snippet that you've shared, which sets bearer token for request Authorization header looks fine to me.
ctx.ExecutingWebRequest +=
delegate(object oSender, WebRequestEventArgs webRequestEventArgs)
{
webRequestEventArgs.WebRequestExecutor.RequestHeaders["Authorization"] =
"Bearer " + result.AccessToken; // accessToken;
};
So your issue could be with either the token itself or some permissions missing for app/users. Here are a few things that you should check:
Required permissions for your App Registered in Azure AD. Make sure the app has permissions for "Office 365 SharePoint Online". Which exact permissions depends on your requirements/use cases, but at least 1 should be there. Also, you should go through with the consent flow either using "Grant Permissions" button or as part of Onboarding flow for the tenant.
Check which "Resource" has the token been acquired for.
At least the sample code link you mention in your question (Active Directory Dot net Webapp Multitenant) mentions the resource as "https://graph.windows.net".
Make sure you have changed that to your SharePoint site collection URL.. something like "https://yoursite.sharepoint.com/".
Do try with including/excluding the / at the end of this URL. I've seen issues just because of that as well, although not sure if yours is related.
If above points don't work, it would be worthwhile to examine the access token you are sending to SharePoint in a tool like https://jwt.io or https://jwt.ms Especially, look for:
"aud" claim, which tells about the intended audience for which this token was issued. It should be your SharePoint URL. If it's not that can be causing the issue.
"tid" claim, to see that token is from correct Azure AD tenant. It will be GUID.
and other claims in token, to see if anything suspicious pops out.
Obvious one, but if you're using delegated permissions, then check that the user has appropriate permissions in SharePoint site collection.

Resources