Azure Active Directory Authentication and SharePoint CSOM - azure

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.

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();
}

Dynamics 365 Business Central Token

I'm attempting to gain access to Business Central Admin Center API, but I'm having some difficulties.
I'm having the idea that it has something to do with the app registration that I have made in the Azure Portal.
I have (as an admin user of the tenant) registered and app and given it "delegated permissions" to "Dynamics 365 Business Central" with access to "Financials.ReadWrite.All".
I have also created a secret for the app.
My problem is that when I try to access the Admin Center API, I get a "403 Forbidden" response, so I assume that I have either forgotten something, I have created my app registration wrong somehow or that my attempt to access the API, is performed in an inaccurate manor.
If I try to examine the token I get, it doesn't show the permissions that I would expect and have seen in other cases (like with MS Graph API), so I'm thinking maybe it's the token that is the problem.
Here is the code that I use to retrieve a token and my attempt to use it afterwards - maybe someone can spot what I'm doing wrong.
Getting the token
var client_id = "removed_for_security_reasons";
var client_secret = "removed_for_security_reasons";
var tenant_id = "removed_for_security_reasons";
var token_url = "https://login.microsoftonline.com/" + tenant_id + "/oauth2/v2.0/token";
var client = new HttpClient();
var content = new StringContent(
"grant_type=client_credentials"+
"&scope=https://api.businesscentral.dynamics.com/.default"+
"&client_id="+ HttpUtility.UrlEncode(client_id) +
"&client_secret="+ HttpUtility.UrlEncode(client_secret));
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/x-www-form-urlencoded");
var response = await client.PostAsync(token_url, content);
// here i print the token so I can check it with jwt.io.
Attempting to use the token
var client = new HttpClient();
HttpRequestMessage req = new HttpRequestMessage();
req.Method = HttpMethod.Get;
req.RequestUri = new Uri("https://api.businesscentral.dynamics.com/admin/v2.11/applications/businesscentral/environments");
req.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(
"Bearer", access_token);
var res = await client.SendAsync(req);
// this results in "403 Forbidden"
There is no further information given as to why this is forbidden, so I'm having a hard time pin pointing what the problem is.
Does anyone have suggestions?
UPDATE 1
OK, so I have tried to follow the description linked. It doesn't describe which permissions box to check though and it's also using PowerShell which I'm not - I'm using C# with HttpClient.
So, to not circle around this any further, please try to explain which to select here (see images) and/or what is wrong/missing.
Image 1 (the app), what is wrong/missing:
Image 2 (permissions 1), what is wrong/missing:
Image 3 (permissions 2), what is wrong/missing: (admin grant doesn't seem to change anything)
After this, I create a client secret and use the code posted initially.
Of cause this isn't working as expected. If the code is wrong, then please point out what the problem is - referring to the description on the web doesn't help me, as it is vague at best.
I think the issue is your combination of delegated permissions and trying to use the client credential flow.
Client credential flow requires application permissions which is also why your delegated permissions are not shown in your token. The client credential flow does not grant you the delegated permissions.
Even though it doesn't seem to be stated directly anywhere that Admin Center API doesn't support client credential flow, I think it is implied in the documentation.
In Using Service-to-Service (S2S) Authentication the Admin Center API is not mentioned in the Feature availability matrix and The Business Central Admin Center API does not mention client credential flow at all and all the example are using user impersonation.
Your App Registration looks okay to me. You will however need to provide the admin consent.
As described in the article I linked above you need to use MSAL (Microsoft Authentication Library). Since you are using C# you need to use MSAL.NET.
I am not an expert on C#, but maybe this quickstart guide could lead you in the right direction.

Asking for user info anonymously Microsoft Graph

In an old application some people in my company were able to get info from Microsoft Graph without signing users in. I've tried to replicate this but I get unauthorized when trying to fetch users. I think the graph might have changed, or I'm doing something wrong in Azure when I register my app.
So in the Azure portal i have registered an application (web app), and granted it permissions to Azure ad and Microsoft graph to read all users full profiles.
Then I do a request
var client = new RestClient(string.Format("https://login.microsoftonline.com/{0}/oauth2/token", _tenant));
var request = new RestRequest();
request.Method = Method.POST;
request.AddParameter("tenant", _tenant);
request.AddParameter("client_id", _clientId);
request.AddParameter("client_secret", _secret);
request.AddParameter("grant_type", "client_credentials");
request.AddParameter("resource", "https://graph.microsoft.com");
request.AddParameter("scope", "Directory.Read.All");
I added the last row (scope) while testing. I still got a token without this but the result is same with or without it.
After I get a token I save it and do this request:
var testClient = new RestClient(string.Format("https://graph.microsoft.com/v1.0/users/{0}", "test#test.onmicrosoft.com")); //I use a real user here in my code ofc.
testRequest = new RestRequest();
testRequest.Method = Method.GET;
testRequest.AddParameter("Authorization", _token.Token);
var testResponse = testClient.Execute(testRequest);
However now I get an error saying unauthorized, Bearer access token is empty.
The errors point me to signing users in and doing the request, however I do not want to sign a user in. As far as i know this was possible before. Have Microsoft changed it to not allow anonymous requests?
If so, is it possible to not redirecting the user to a consent-page? The users are already signed in via Owin. However users may have different access and i want this app to be able to access everything from the azure ad, regardless of wich user is logged in. How is the correct way of doing this nowadays?
Or am I just missing something obvious? The app has been given access to azure and microsoft graph and an admin has granted permissions for the app.
Edit: just to clarify, i tried both "Authorization", "bearer " + _token.Token, and just _token.Token as in the snippet.
Yes, it's still possible to make requests to Graph without a user present using application permissions. You will need to have the tenant admin consent and approve your application.
Edit / answer: Adding the 'Authorization' as a header instead of a parameter did the trick. It works both with 'bearer token' and just 'token'

How can I use an access token obtained from the Microsoft Graph API to access other APIs

My team and I built Web App that uses the Microsoft Graph API to recover data from a user's Office 365 environment. The application uses Azure AD to provide users with an access token.
We're now trying to add a component that can access the same user's Exchange Online informations using the EWS Api. However, it seems like trying to use the same access token as the one provided by the Graph API always returns a 401 (Unauthorized) response even if I have to proper permissions set on my Azure AD Application. Here is the code we use to try and access some user's information:
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2013);
service.HttpHeaders.Add("Authorization", "Bearer " + accessToken);
service.PreAuthenticate = true;
service.SendClientLatencies = true;
service.EnableScpLookup = false;
service.Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx");
TasksFolder tasksfolder = TasksFolder.Bind(service, WellKnownFolderName.Tasks,
new PropertySet(BasePropertySet.IdOnly, FolderSchema.TotalCount));
The Bind method will always return a 401 error which prompts me to think that ther access token from one API is not valid for another one.
If that's the case, is it possible to get a single access token that will be valid for mutiple API calls ?
It should work okay but EWS doesn't support the constrained permissions model like REST so " EWS applications require the "Full access to user's mailbox" permission. as per the note in https://msdn.microsoft.com/en-us/library/office/dn903761(v=exchg.150).aspx

Resources