MS Graph API and DriveItem search not working with client credentials flow - node.js

I'm trying to call the search endpoint of the OneDrive API on a drive (i.e. https://graph.microsoft.com/v1.0/drives/{drive-id}/root/search(q='mysearchterm').
This works fine on the Graph Explorer, however, I'm not getting any search results with the client credentials flow on the same drive.
My app registration has all the required application permissions mentioned in the API documentation (Files.Read.All, Files.ReadWrite.All, Sites.Read.All, Sites.ReadWrite.All) and reading drives, driveitems, downloading drive items is all working fine. The one thing that is not working, is searching on drive items. I'm just getting an empty array back, no errors;
{"#odata.context":"https://graph.microsoft.com/v1.0/$metadata#Collection(driveItem)","value":[]}
I'm using adal-node with acquireTokenWithClientCredentials.
var adal = require("adal-node");
const TENANT = "{tenant-name-here}.onmicrosoft.com";
const CLIENT_ID = "{Application-id-here}";
const CLIENT_SECRET = "{Application-key-here}";
function getToken() {
return new Promise((resolve, reject) => {
const authContext = new adal.AuthenticationContext(
`https://login.microsoftonline.com/${TENANT}`
);
authContext.acquireTokenWithClientCredentials(
GRAPH_URL,
CLIENT_ID,
CLIENT_SECRET,
(err, tokenRes) => {
if (err) {
reject(err);
}
resolve(tokenRes.accessToken);
}
);
});
}
The drive I'm searching on is a SharePoint document library.

Some important tip: client credentials flow need to register the app in the Azure Management Portal but not Applicaation Registeration Portal. Graph Explorer is mostly based on the later one, so they have different backend code now is normal. Maybe they will do the same logic in furture.
We strongly recommend that you use Microsoft Graph instead of Azure AD Graph API to access Azure Active Directory resources. Our development efforts are now concentrated on Microsoft Graph and no further enhancements are planned for Azure AD Graph API. There are a very limited number of scenarios for which Azure AD Graph API might still be appropriate; for more information, see the Microsoft Graph or the Azure AD Graph blog post in the Office Dev Center.
adal-node is not the same as Graph, so you get result in the graph explorer but not the NodeJS product. We suggest you to use the latest Graph API.
Official docs: https://learn.microsoft.com/en-us/javascript/api/overview/azure/activedirectory?view=azure-node-latest

Related

How can I get the azure AD roles in my Backend?

I am developing a backend in node express where I use the passport-azure-ad library to protect the routes of my api, is there any way to access the roles defined in Azure Ad for the application and validate them in the routes?
To achieve the above requirement you may need to follow the below workaround.
We can get our Azure AD log details by using MS GRAPH Programmatically
SAMPLE CODE:-
const options = {
authProvider,
};
const client = Client.init(options);
let directoryAudit = await client.api('/auditLogs/directoryAudits/{id}')
.get();
Also you can get roles which has assigned in Azure AD by using below MS GRAPH query in your code.
GET /users/{id | userPrincipalName}/appRoleAssignments
For complete setup please refer the below links:
MS DOC:- Call the Microsoft Graph API in a Node.js console app.
SO THREAD:- How to issue tokens from Azure AD in a Node.js App/API?

OAUTH / Azure Functions: Method to auth AAD user for endpoints that don't support service principals

I've been leveraging Azure Function Apps to automate items in Azure. I currently have working functions that connect to Microsoft Graph, Resource Explorer, KV etc. using service principal / OAUTH client credentials flow (inside the function app). To call my function app, I've implemented implicit flow. While I'm not an expert at OAUTH, I am familiar enough now to get this configured and working.
However, there are Azure endpoints I need to use that don't support using a service principal token, they only support an actual AAD user requesting a token. Here's one that I want to run: Create synchronizationJob
If you look at the permissions section of the above link, you'll see that "application" is not supported. I did test this in a function: I can run these endpoints in Graph Explorer fine (as myself), but they fail in the function when using a token linked to a service principal.
Since this new automation is going to be an Azure Function (and not an interactive user), I can't use the authorization code flow. I need this service account's OAUTH to be non-interactive.
TL;DR
I can run the above endpoint in Azure's Graph Explorer just fine:
Azure Graph Explorer
since I'm authenticating as myself, and have a token generated based on my user ID. But for automating using Azure Functions where I need to use this endpoint (which doesn't support OAUTH using an SP), I need some way to have a back-end AAD user auth and pull a token that can be used to run the endpoint.
Any help is welcome! Feel free to tell me that I'm either missing something very basic, or not understanding a core principal here.
As juunas mentioned no guarantee that will work though, I test in my side and it seems doesn't work although I assigned "Global administrator" role to the service principal.
For your situation, you can request the access token in your function code and then use the access token to request the graph api.
Add the code like below in your function to get access token.
HttpClient client = new HttpClient();
var values = new Dictionary<string, string>
{
{ "client_id", "<your app client id>" },
{ "scope", "<scope>" },
{ "username", "<your user name>" },
{ "password", "<your password>" },
{ "grant_type", "password" },
};
var content = new FormUrlEncodedContent(values);
var response = await client.PostAsync("https://login.microsoftonline.com/<your tenant id>/oauth2/v2.0/token", content);
var responseString = await response.Content.ReadAsStringAsync();
var obj = JObject.Parse(responseString);
var accessToken = (string)obj["access_token"];
And then use the access token got above to request graph api.

Access a blob file via URI over a web browser using new AAD based access control

With the announcement of Azure Storage support for Azure Active Directory based access control, is it possible to serve a blob (a specific file) over a web browser just by it's URI?
The use case I want to simplify is giving a few people access to files on the blob without the need of having to append a SAS token to the URI. Instead it would be brilliant to have the typical OAuth flow started when trying to open the plain URI in his/her web browser.
In my case we want to give access to files that have been uploaded to the blob storage by users through our support bot, build on Microsoft Bot framework. Links in our support system should be accessible by a support agent in their web browser of choice.
It this use case supported by this announcement or does this only work for coded OAuth flows, meaning we still have to implement some code?
If so, is there a good sample on how to start the OAuth flow from a Azure Function app and use the resulting token to download the file (over Azure Storage REST endpoint)?
While this answer is technically correct, it wasn't a direct response to my initial question.
I was looking for a way to provide the direct uri of any blob to business users, so they can simply open it in any web browser and see the file.
In my case we wanted to give access to files that have been uploaded to the blob storage by users through our support bot, build on Microsoft Bot framework. E.g. serving the attachment as a link in our support system to be accessed by a support agent.
After digging into this, I can answer the question my self:
With the announcement of Azure Storage support for Azure Active Directory based access control, is it possible to serve a blob (a specific file) over a web browser just by it's URI?
No, this is not possible. More specifically, simply opening the direct uri to a blob in the browser doesn't trigger the OAuth flow. Instead it will always give you ResourceNotFound response unless you provide a SAS query token or set the blob to public. Both solutions are bad from security perspective (when normal users involved) and obviously bad UX.
Solution
Looking for a way to achieve exactly what I want, I came up with the idea of a azure function serving the attachment to any business user by passing the fileName as url parameter and constructing the path using a route template.
Thinking of security and the need for an access token anyway, you could protect the function app through platform authentication (a.k.a. easyAuth).
However, this is not enough and configuring all parts of the solution is not straight forward. That is why I'm sharing it.
TL;DR high-level steps:
Create a new Function App (v2 recommended)
Enable the function App for authentication (easyAuth)
Create a service principal (a.k.a. app registration) for the function app (implicit by step 2)
Add additional allowed token audience https://storage.microsoft.com on the app registration
Edit the manifest of the app registration to include Azure Storage API permission (see special remarks below)
Modify authSettings in Azure Resource explorer to include additionalLoginParams for token response and resourceId
Give at least the Storage Blob Data Reader permission on the blob to all users accessing the files
Deploy your function app, call it, access the user token, call the blob storage and present the result to the user (see code samples below)
Remarks on Azure Storage API permission and access token (Step 5 & 6)
As stated in the latest documentation for AAD authentication support on azure storage, the app must grand user_impersonation permission scope for resourceId https://storage.azure.com/. Unfortunately the documentation did not state on how to set this API permission as it is not visible in the portal (at least I did not find it).
So the only way is to set it through its global GUID (can be found on the internet) by editing the app registration manifest directly in the azure portal.
Update:
As it turned out, not finding the right permission in the portal is a bug. See my answer here. Modifying the manifest manually results in the same, but directly doing it in the portal is much more convenient.
"requiredResourceAccess": [
{
"resourceAppId": "e406a681-f3d4-42a8-90b6-c2b029497af1",
"resourceAccess": [
{
"id": "03e0da56-190b-40ad-a80c-ea378c433f7f",
"type": "Scope"
}
]
},
{
"resourceAppId": "00000002-0000-0000-c000-000000000000",
"resourceAccess": [
{
"id": "311a71cc-e848-46a1-bdf8-97ff7156d8e6",
"type": "Scope"
}
]
}
]
The first one is the user_impersonation scope on Azure Storage and the second is the graph permission for User.Read, which in most cases is helpful or needed.
After you uploaded your modified manifest, you can verify it on the API Permissions tab on your app registration.
As easyAuth is using the v1 endpoint of AAD, your app needs to request those permission statically by passing resource=https://storage.azure.com/ when triggering the OAuth flow.
Additionally Azure Storage requires the bearer schema for authentication header and therefore a JWT token is needed. To get a JWT token from the endpoint, we need to pass response_type=code id_token as an additional login parameter.
Both can only be done through Azure Resource explorer or powershell.
Using Azure Resource explorer you have to navigate all your way down to the authSettings on your function app and set the additionalLoginParams accordingly.
"additionalLoginParams": [
"response_type=code id_token",
"resource=https://storage.azure.com/"
]
Code Sample
Here is a complete code sample for an easy azure function using all aboves mechanisms.
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
namespace Controller.Api.v1.Org
{
public static class GetAttachment
{
private const string defaultContentType = "application/octet-stream";
[FunctionName("GetAttachment")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "v1/attachments")] HttpRequest req,
ILogger log)
{
if (!req.Query.ContainsKey("fileName"))
return new BadRequestResult();
// Set the file name from query parameter
string fileName = req.Query["fileName"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
fileName = fileName ?? data?.name;
// Construct the final uri. In this sample we have a applicaiton setting BLOB_URL
// set on the function app to store the target blob
var blobUri = Environment.GetEnvironmentVariable("BLOB_URL") + $"/{fileName}";
// The access token is provided as this special header by easyAuth.
var accessToken = req.Headers.FirstOrDefault(p => p.Key.Equals("x-ms-token-aad-access-token", StringComparison.OrdinalIgnoreCase));
// Construct the call against azure storage and pass the user token we got from easyAuth as bearer
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.Value.FirstOrDefault());
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate");
client.DefaultRequestHeaders.Add("Accept", "*/*");
client.DefaultRequestHeaders.Add("x-ms-version", "2017-11-09");
// Serve the response directly in users browser. This code works against any browser, e.g. chrome, edge or even internet explorer
var response = await client.GetAsync(blobUri);
var contentType = response.Content.Headers.FirstOrDefault(p => p.Key.Equals("Content-Type", StringComparison.OrdinalIgnoreCase));
var byteArray = await response.Content.ReadAsByteArrayAsync();
var result = new FileContentResult(byteArray, contentType.Value.Any() ? contentType.Value.First() : defaultContentType);
return result;
}
}
}
}
If you want to use Azure Active Directory based access control for the storage, what you need to get is the access token. Here are the steps for your reference.
Register an application
2.Assign a build-in RBAC role to this application It depends on you which role to be assigned to the application.
3.Get the access token.
4.With the access token, now you can call the storage rest api.

Consume Secure Azure API from SharePoint online

I have developed Azure API app with authentication on feature, log in with Azure Active Directory, I need to consume this API from SharePoint online , I
I need to authenticate and consume the azure API, no signin-prompt, every thing should be handled in the script
need to use ADAL.js to authenticate secure API ,I cannot find any good reference about the JavaScript code, I was wondering if anyone have a good reference how the JavaScript code should look like?
Thanks!
Here are the steps to call the azure hosted API from SharePoint online using JavaScript and ADAL.js library, no signing-prompt, everything should be handled in the script using ADAL.js to authenticate secure API
Create and Configure API
Create azure API  
Publish your azure API in azure
Browse to azure portal, select your API application , select Authentication/Authorizations
Set the App Service Authentication: On
Action to take when request us not authenticated: Log in with Azure dictionary
Authentication providers: Express
Now the API is protected with Azure AD, if you navigate your API via browser you will be prompted for login
When we set authentication in Express mode, the app will be created automatically in Azure Active directory, you can see the name under Azure AD app
Navigate to Azure management portal, click on active directory in left navigation,
Click on the Directory which will be federated to your office 365 (or any source you want to call azure API which uses the same azure active directory as your configured for you API authentication)
Click on the Application, and you will find you AD app in the list which has been created with Express method as we discussed om step nr.3
Now we need to create new app in AAD which will be our communication channel from office 365 to Azure API, Click on ADD on footer
Enter name and select “WEB APPLICATION AND/OR WEB AP” option
For Sign in URL enter your SharePoint online Url which you are planning to call Azure API from
For APP ID URL, enter unique Url, this will be used as a unique logical identifier for your app.
After the app created, click on Configure, copy the Client ID which will be used later­­
Under Permission to other applications, Click “Add Application”, on the next page select “All Apps” and select you Azure API app which you double checked in step nr.8, and confirm
You will be redirected back to Configure page, Under Permission to other applications, now you see your azure API app is listed here, click on delegated permission, select the access to app
At the bottom of the page, click Manage manifest > Download manifest.
Download the file to a location where you can edit it.
In the downloaded manifest file, search for the oauth2AllowImplicitFlow property. Change the value of this property from false to true, and then save the file.
Click Manage manifest > Upload manifest, and upload the file that you updated in the preceding step.
Azure management portal, select setting and copy your AAD subscription ID for related AAD
Call Azure API from SharePoint Online
Once the above steps have been done, you can call azure API from sharePoint online which is using same Active directory above
Edit the page and add Script Editor web part
Add following script
· subscriptionId, see step nr.20
· clientId see step nr.13
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://secure.aadcdn.microsoftonline-p.com/lib/1.0.13/js/adal.min.js"></script>
<script type="text/javascript">
function CallAzureAPI() {
"use strict";
var subscriptionId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
var clientId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
window.config = {
subscriptionId: subscriptionId,
clientId: clientId,
postLogoutRedirectUri: window.location.origin,
endpoints: {
AzureApiUri: 'https://xxxxxxxxxxxx.azurewebsites.net'
},
cacheLocation: 'localStorage'
};
var authContext = new AuthenticationContext(config);
var isCallback = authContext.isCallback(window.location.hash);
authContext.handleWindowCallback();
if (isCallback && !authContext.getLoginError()) {
window.location = authContext._getItem(authContext.CONSTANTS.STORAGE.LOGIN_REQUEST);
}
// If not logged in force login
var user = authContext.getCachedUser();
if (user) {
// Logged in already
console.log(user);
}
else {
authContext.login();
}
// Acquire token for Files resource.
authContext.acquireToken(config.endpoints. AzureApiUri, function (error, token) {
// Handle ADAL Errors.
if (error || !token) {
console.log('ADAL error occurred: ' + error);
return;
}
var ApiUri = "https://xxxxxxxxx.azurewebsites.net/api/Get";
$.ajax({
type: "GET",
url: ApiUri,
headers: {
'Authorization': 'Bearer ' + token,
}
}).done(function (response) {
console.log('Successfully called API.');
console.log(response);
}).fail(function () {
console.log('Calling API failed.');
});
});
}
</script>
<input type='button' value='Call Azure API' onclick=" CallAzureAPI ();"/>
This solution works though after some time ( later I found out when AAD cookie is expired ) we get this error "Token renewal operation failed due to timeout ",
I did some research and I found out he getCachedUser or getUser methods look into the browser storage for id_token and returns a non-null user if there is a token inside the cache. It does not look into the token expiration time though.
What's happening here is since localStorage is used, tokens are preserved in the cache when one re-opens the browser (and hence getCachedUser returns a non-null object) but the AAD cookie is expired (unless user checked the keep me signed in checkbox when logging in). Since the cookie is expired, acquire token call fails with the "login required" error.
so as workaround I checked , keep me signed in checkbox when logging in and it works .
It is possible to call the web API which protected by Azure AD from the SharePoint online using the JavaScript, however it is very complex.
Here are the steps for your reference:
Developing the web API which protected by Azure AD
register an native app on the same tenant
enable the implicit flow for the native app
grant the native app to access the web API from Azure portal
using the admin_consent for the native app to grant permission fo the organization
developing a web page in web API project for the redrect page for the OAuth 2.0 request
write the code in the web page using windows.postMessage to post the token to the parent page
$().ready(function () {
if (window.parent != null) {
// get the token from URL here
var token = "123";
console.log(window.location);
window.parent.postMessage(token, "*");
}
})
In SharePoint online page, using iframe to send the implict flow like code below
<iframe id="iframe_id" src="https://login.microsoftonline.com/{tenanit}.onmicrosoft.com/oauth2/authorize?response_type=token&client_id={clientId}&resource={webApiAppIdUri}&redirect_uri={redirect_uri}&prompt=none"></iframe>
<script>
var token = "";
window.addEventListener("message", receiveMessage, false);
function receiveMessage(event) {
token=event.data;
console.log(event.data);
}
</script>
Here is a figure to help understanding the progress:

Get the filtered activedirectoryclient.users from azure adfs using like

I am trying to get few users from Azure ADFS within my domain. I am able to get all of my users from my integrated Office 365. But my requirement is to get the filtered user lists. Can we do something like below, I have tried. But it doesn't return anything even if I have few users with that.
ActiveDirectoryClient activeDirectoryClient = new ActiveDirectoryClient(serviceRoot,
async () => await GetTokenForApplication());
var result0 = await activeDirectoryClient.Users
.Where(u => u.GivenName.Contains("sibeesh"))
.ExecuteAsync();
var result1 = await activeDirectoryClient.Users
.Where(u => SqlMethods.Like(u.DisplayName, "sib%"))
.ExecuteAsync();
And also how can we get the users from other domains which we have in our ADFS? How can we group those users accordingly. I am totally new to configuring ADFS, any help is much appreciated. Thanks in advance.
Just to get the terms right.
There is no such thing as Azure ADFS.
Azure AD is in the cloud. ADFS is on-premises and is not an Azure product.
You are using the Azure AD Graph API Client Library and there are some good examples of syntax here.
This client library does not talk to ADFS.
Azure AD does not have the concept of domains - rather tenants.
Please clarify "And also how can we get the users from other domains which we have in our ADFS?"

Resources