I'm trying to upload and download files in my sharepoint online using a background task (daemon) that runs frequently in my ASP.NET Core app. Because it's a background task, no user identity is used. Instead, I tried to follow this document, getting an access token using the https://graph.microsoft.com/.default scope, as well as having my enterprise app on azure having particular permissions already granted by the admin.
I'm able to get an access token using my app's client id and secret. However when I try to request the drives on a particular site on sharepoint, it just stalls, hinting me that it can't reach the path. I can reach this same path totally fine when I use my user credentials instead.
I think I may be missing a step or some azure administration-related task. Below is the code snippet that shows I can get the access token, but stalls when getting the drives.
var client = new ConfidentialClientApplication(id, uri, cred, null, new SessionTokenCache());
var authResult = await client.AcquireTokenForClientAsync(new[] {"https://graph.microsoft.com/.default"});
var token = authResult.AccessToken; // get token successfully
var graphServiceClient = new GraphServiceClient(new DelegateAuthenticationProvider(async request => {request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token)}));
// stalls below
var drives = await graphServiceClient.Sites[<sharepoint_host>].SiteWithPath(<known_path>).Drives.Request().GetAsync();
Using Microsoft Graph SDK within an ASP.NET Core 2 app.
Edit: Below is an updated screenshot showing application permissions added and consented:
Based on your image you have granted delegated permissions to the app. You need to grant application permissions. Delegated permissions only apply when acting on behalf of a user.
Related
I have been trying to implement a solution for this for days. It's my first experiment with Microsoft Graph. I had our network admin register the app and went through the quick start code in console-app-quickstart.
I looked at active-directory-dotnetcore-daemon-v2 and active-directory-dotnet-iwa-v2.
var App = PublicClientApplicationBuilder
.Create("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")
.WithTenantId("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")
.Build();
The PublicClientApplication has the AcquireTokenByIntegratedWindowsAuth function. This sounds good because we can launch the console app as whatever user we want to use with a scheduled task. But it errors out with WS-Trust endpoint not found. Where's WS-Trust endpoint defined?
The sample also includes the line var accounts = await App.GetAccountsAsync() but that always returns zero accounts. Some responses to searches for this say that we have to use the global tenant admin. The company doesn't like that idea at all. How can that be safe? Do we create a new user as an admin tenant just for that?
The other option is this
var App = ConfidentialClientApplicationBuilder.Create("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")
.WithClientSecret("aeiou~XXXXXXXXXXX")
.WithAuthority(new Uri("https://login.microsoftonline.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"))
.Build();
The ConfidentialClientApplication doesn't have the integrated windows auth version. I can get connected and get MailFolders and Messages and process those, but it seems to work only when we use App.AcquireTokenForClient(scopes) and API permissions that allow the app to read everyone's email. Security doesn't like that much either.
I also looked at impersonation-and-ews-in-exchange. I read in some places that ExchangeWebService is deprecated and use MS Graph instead. Is the MS Graph API permissions in the EWS category mean that it's going to be around?
Can anyone out there show me the right combination of pieces needed to do this? (api permissions, client application type, scopes, authority, etc). It needs to be unattended (launched by scheduled task), needs to have permissions to read only one email box, and save the attachments.
(sorry so long)
Thanks, Mike
WS-Trust endpoint not found
The WS-Trust endpoint is your ADFS endpoint, if you have ADFS 2019 then MSAL does support that using WithAdfsAuthority see https://github.com/MicrosoftDocs/azure-docs/blob/main/articles/active-directory/develop/msal-net-initializing-client-applications.md
There are some other restriction around using WIA that are listed at the top of https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/Integrated-Windows-Authentication-in-MSAL-2.x . If the constraints don't affect you it should work okay.
With the Client Credentials flow which is what your using above you can restrict the scope of the mailboxes it can access see https://learn.microsoft.com/en-us/graph/auth-limit-mailbox-access
I would stick with the Graph rather then EWS as the later is being phased out and requires more permissions as its a legacy API.
The tutorial you shared in the question is an asp.net core console app. Since you want to have a console app and use it to read exchange mails.
Therefore, what we can confirm is that: We need to use MS Graph API to read the exchange mails. Graph API required an Azure AD application with correct API permissions to generate Access token to call the API. API permissions have 2 types, Delegated for Web app because it required users to sign in to obtain the token, Application for daemon app like console application which don't require an user-sign-in.
Since you are using the asp.net core console application, you can only using Application API permission. Using Application permission means the console app has the permission to query messages of any email address in your tenant. You can't control the Graph API itself to query some specific users only. But you can write your own business logic to set authorization.
Then we can make the console application authorized to access the API, we can generate an Access token and use it in the HTTP request header to call the API, we can also use the Graph SDK. Using SDK will help to troubleshoot when met error.
using Microsoft.Graph;
using Azure.Identity;
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "tenant_id";
var clientId = "Azure_AD_app_id";
var clientSecret = "Azure_AD_client_secret";
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
var messages = await graphClient.Users["{email_address/user_account/user_id}"].Messages.Request().Select("sender,subject").GetAsync();
I am using Microsoft.Identity.Claim library to conenct to azure and authenticate user.
My first idea was to use AcquireTokenByIntegratedWindowsAuth method but that requires few days until network administrator people investigate how to enable single sign-in option and change that user are now "federated" and not "managed. So I now switched to AcquireTokenInteractive method because chances are that ure will be logged in, so he will just need to choose account from automatically opened browser and that's it. No big deal.
And this works:
string clientId = "xxx";
var tenantId = "yyy";
string[] scopes = new string[] { "User.Read", "User.ReadBasic.All"};
AuthenticationResult result;
var app = PublicClientApplicationBuilder.Create(clientId)
.WithRedirectUri("http://localhost")
.WithAuthority(AzureCloudInstance.AzurePublic, tenantId).Build();
try
{
result = await app.AcquireTokenInteractive(scopes)
.ExecuteAsync();
}
catch (MsalUiRequiredException) //see all possibl exceptions!
{
However, I don't receive claims inside token.
My idea is to send this token to server, then validate it, and if sucessfull create user in database and then use my own authenication mechanism I use for other users (that are not part of domain, completely separate user).
But I don't want all users from domain have access to this app.
So I would like to get claims, roles...
How to get claims, using this or any other lib given user email, or some other unique data?
Here I summarize all of the steps you may need to follow:
1. You need to register a app in azure ad for your service app, you can refer to this document
2. Then you need to register another app in ad as the client app, you can follow the steps in this document
3. After that, you need to do the steps in this document which I already provided in comments. Do it in the service app but not client app.
4. Then you can get the access token with your code and check the claim roles. Please note, do not add microsoft graph permissions into scopes in your code. You need to add api://<the client id of the registered app for service app>/.default into scopes.
5. Now you can find the claim roles in your access token.
Any user who logged into our system (IdentityServer as Auth) under a specific tenant should be able to create an event as an online meeting (MS Teams).
We followed Build ASP.NET Core MVC apps with Microsoft Graph and Create and enable an event as an online meeting to create an application that authenticates an AD user of an organization and allow him to create an event as an online meeting.
We are able to implement it successfully and was able to create the event as an online meeting.
But the exact scenario here is any user who is authenticated in our web application (not a AD user) should be able create a MS Teams meeting event and share it with other participants who should be able to join the meeting.
I am not sure how to achieve this.
Edit
Or at least how do I create onlineMeeting ? I tried with Client credentials provider as below
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create("<<App_Id>>")
.WithTenantId("<<Tenant_Id>>")
.WithClientSecret("<<Client_Secret>>")
.Build();
ClientCredentialProvider authenticationProvider = new ClientCredentialProvider(confidentialClientApplication);
GraphServiceClient graphClient = new GraphServiceClient(authenticationProvider);
var onlineMeeting = new OnlineMeeting
{
StartDateTime = DateTimeOffset.Parse("2020-01-15T21:30:34.2444915+05:30"),
EndDateTime = DateTimeOffset.Parse("2020-01-15T22:00:34.2464912+05:30"),
Subject = "User Token Meeting"
};
var meeting = graphClient.Me.OnlineMeetings
.Request()
.AddAsync(onlineMeeting).Result;
but it was throwing
Code: Forbidden
Inner error:
AdditionalData:
request-id: <<some_id>>
date: 2020-07-09T16:42:23
ClientRequestId: <<some_id>>
I been working on your question in few days, I was going to mention some of the suggestions comes with the other answer. But in addition, the main challenge here, the system need to know who is authorized to do what.
So IMO The Best choice to solve this is creating a guest login in AzureAD, than you can use that to create a Team Events. Further more you can added an extra step after guest user logon, so that guest should enter his/her name and use it as reference.
You will need to take these two steps.
Get the right token
Create an event (but change the url to
https://graph.microsoft.com/v1.0/users/the_user#domain.com/events)
The hard part is getting the right token, you have multiple options.
Use the client credentials flow this will force an admin from every new tenant to authorize your application for their organization. You can then use the tenant from the user info to request a token for that tenant and use the user id to create the right url to post to.
Make IdentityServer save the access token and allow you to access it. At coonfiguration level you have access to token callback and there you can also save the Azure AD access token. I think you can add it to a reference token, that way it isn't transmitted everytime but your web application is still able to access it.
Use the on-behalf-of flow, this would require you to pass the Azure AD access token token retrieved from azure AD by the IdentityServer to be passed to your application.
Just remove the identity server from the flow and have your web application logging straight with Azure AD. That way you'll have the right token available all the time.
Edit
After reading your editted question, what you want is a website where the user doesn't have to be an member of your Azure AD, just wants access to some new online meeting?
Best option is to created a shared mailbox, authorize an application (with Calendar.ReadWrite). Get a token with client credentials and call Create Event and then extract the meeting url from the event (that you'll get back when the posts completes succesfully.
To create an online meeting for the "Client Credentials" flow, I used the following:
var confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create("my-app-client-id")
.WithTenantId("my-aad-tenant-id")
.WithClientSecret("my-client-secret")
.Build();
var authProvider = new ClientCredentialProvider(confidentialClientApplication);
var graphClient = new GraphServiceClient(authProvider);
var meetingGuid = Guid.NewGuid();
var createMeetingResponse = await graphClient.Users["my-aad-user-object-id"].OnlineMeetings
.CreateOrGet(meetingGuid.ToString())
.Request()
.PostAsync();
The issue with your code is that referencing graphClient.Me causes the Graph requests to go to https://graph.microsoft.com/v1.0/me/onlineMeetings, which is not what you want in the "Client Credentials" flow. See this screenshot from the documentation found here: https://learn.microsoft.com/en-us/graph/api/onlinemeeting-createorget?view=graph-rest-1.0&tabs=csharp
I had to grant "application" permissions in Azure Portal to allow my app to access the online meetings API, and I had to create a client secret. I also had to follow this article to create a policy and grant it to specific users using Microsoft Teams PowerShell:
https://learn.microsoft.com/en-us/graph/cloud-communication-online-meeting-application-access-policy
For users not in your organization, you can invite them as a guest user to your tenant.
I had issues using the Microsoft Teams Powershell commands due to settings in Windows Remote Management, which I did something like this to work around:
https://lonesysadmin.net/2017/08/10/fix-winrm-client-issues/
I'm trying to read the user's data from Azure Active Directory via Microsofts' Graph API. Using the Graph Explorer I'm able to get all users but using a stand alone application I end up with an "unauthorized" response after receiving a token. I'm clearly missing some steps but it isn't obvious to me what steps that would be. Any insight would be appreciated
The code below is based off a MSFT sample:
// config values
// authority = "https://login.microsoftonline.com/{ TENANT ID }/oauth2/"
// resource uri = "https:// APP NAME .azurewebsites.net";
// graph uri = https://graph.windows.net/TENANT ID/ also tried https://graph.windows.net/v1.0/
// short form
public async void GetUsers( ADConfiguration config )
{
_authContext = new AuthenticationContext(config.GetAuthority());
_clientCredential = new ClientCredential(config.ClientId, config.ClientSecret);
AuthenticationResult result = null;
// obtain the token, this part is still successful
result = await _authContext.AcquireTokenAsync(config.ResourceUri, _clientCredential );
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
string address = config.GetGraphiUri() + "users?api-version=1.6";
// this response is always unauthorized
HttpResponseMessage response = await _httpClient.GetAsync(address);
}
In addition to answer your new problem . From you code , you are acquiring token using client credential flow. In the client credentials flow, permissions are granted directly to the application itself.
Since you are using Azure AD Graph API , you need to add application permission :
In the Azure portal, choose your application, click on Settings
In the Settings menu, choose the Required permissions section ,select Windows Azure Active Directory(Azure ad graph api) , add related application permissions your app requires .
Inside your app's blade, hit Grant Permissions to do admin consent with your admin's credential .
Your config values seem off:
Authority should be: https://login.microsoftonline.com/{TENANT ID}.
It seems to me that you are trying to use Azure AD Graph API, not Microsoft Graph API.
In that case:
Resource URI should be: https://graph.windows.net/. (MS Graph API is https://graph.microsoft.com/)
I am trying to use the Windows Azure Active Directory (WAAD) Graph API to add an application to my WAAD tenant. I have successfully used the API to create users. When using the API to add an application I receive an Authorization exception:
Authorization_RequestDenied: Insufficient privileges to complete the operation
Performing the same steps to add a user works without exception.
I followed the guide here: http://msdn.microsoft.com/en-us/library/windowsazure/dn151791.aspx#BKMK_Configuring and the samples here: http://code.msdn.microsoft.com/Write-Sample-App-for-79e55502 to get started.
Here is a sample of my code:
//get the tenantName
var tenantName = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
// retrieve the clientId and password values from the Web.config file
var clientId = ConfigurationManager.AppSettings["ClientId"];
var password = ConfigurationManager.AppSettings["Password"];
// get a token using the helper
var token = DirectoryDataServiceAuthorizationHelper.GetAuthorizationToken(tenantName, clientId, password);
// initialize a graphService instance using the token acquired from previous step
var graphService = new DirectoryDataService(tenantName, token);
// Create and save the application
var application = new Application();
application.availableToOtherTenants = false;
application.displayName = "some display name";
application.homepage = "https://localhost/";
application.identifierUris.Add("https://localhost/");
application.replyUrls.Add("https://localhost/");
graphService.AddTodirectoryObjects(application);
graphService.SaveChanges();
Do I need to setup rights to allow the creation of Applications via the Graph API? I was unable to find a location in the Azure Management Console that allowed me to do this.
Am I using the correct code to add an Application? There are not many examples on how to work with Applications. I assume I need to use the AddTodirectoryObjects to save an Application because I am not finding an "AddTo..." method for Applications.
It seems your service principal is in the wrong role. I guess it's under User Account Administrator role. Try to add it to other role e.g.: Company Adminstrator for testing purpose...