I want to use the communications API in Microsoft Graph using a simple C# desktop app.
For now, let's say I want to use the GET /communications/calls/{id} request that can only be used via Application Permissions.
From my understanding, only bots can use the communications API.
So I've created a bot in Azure and deployed this code:
https://github.com/microsoft/BotBuilder-Samples/tree/main/samples/csharp_dotnetcore/24.bot-authentication-msgraph
The bot works and I get the user mail etc. but:
The bot uses delegated credentials that are not supported for the Get Call request.
I want to access the API via a C# desktop app using the bot credentials and not using the bot itself(I don't know if this is even possible but logic dictates that it should)
When I try a simple API request using the bot's app credintials (like I would use any other Azure app) I get this response:
{"error":{"code":"UnknownError","message":"","innerError":{"date":"2022-03-31T09:29:44","request-id":"7906abfd-5ba4-439f-939c-42361c2ab255","client-request-id":"7906abfd-5ba4-439f-939c-42361c2ab255"}}}
which is not much help.
I've tried different ways like getting the JWT token for the bot and then using it to get an On-behalf-of provider as shown here:https://learn.microsoft.com/en-us/graph/sdks/choose-authentication-providers?tabs=CS and I get this exception:
MsalUiRequiredException: AADSTS50058: A silent sign-in request was sent but no user is signed in.
The code:
var scopes = new[] { "User.Read" };
var tenantId = "common";
var clientId = "appId";
var clientSecret = "appSecrete";
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
var oboToken = "JWT token I get form the bot with scope:appId/.default";
var cca = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.WithClientSecret(clientSecret)
.Build();
var authProvider = new DelegateAuthenticationProvider(async (request) =>
{
var assertion = new UserAssertion(oboToken);
var result = await cca.AcquireTokenOnBehalfOf(scopes, assertion).ExecuteAsync();
request.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", result.AccessToken);
});
var graphClient = new GraphServiceClient(authProvider);
string callId = "callId";
var call = graphClient.Communications.Calls[$"{callId}"].Request().GetAsync();
call.Wait(); //-->here exception
var res = call.Result;
So firstly I want to know is it possible to use the communications API from a desktop app using the bot credentials?
If so then I would really appreciate a good explanation of a step-by-step on how to get the Get Call request to work,
specifically how to set up the bot, how to get the correct Access Token for the Graph API.
If not, I would appreciate a code example of using the Get Call API request from inside the bot.
Related
I am building a feature that automates the retrieval of documents and other SharePoint files from a Web API, but I'm having a difficult time getting authorized to perform even basic read operations. I am testing this in a .NET 6 console application using the Microsoft.SharePointOnline.CSOM NuGet package.
I have registered an application in Azure Active Directory and given it the Sites.Read.All permission. I've taken the ClientID, ClientSecret and TenantID as reported by that registered application and I'm using those in my console application. I can retrieve an access token without issue, and decoding that JWT shows that it comes with Sites.Read.All permission. But regardless of what I try, ClientContext.ExecuteQueryAsync() consistently throws an exception complaining that the remote server responded with a 401.
Here is the code that I'm testing this with:
var clientId = "myClientId";
var clientSecret = "myClientSecret";
var tenantId = "myTenantId";
var authority = "https://login.microsoftonline.com/" + tenantId;
var siteUrl = "https://myorg.sharepoint.com";
var app = new ConfidentialClientApplicationBuilder
.Create()
.WithClientSecret(clientSecret)
.WithAuthority(authority)
.WithTenantId(tenantId)
.Build();
var paramBuilder = app.AcquireTokenForClient(new[] { siteUrl + "/.default" });
var authResult = await paramBuilder.ExecuteAsync();
// authResult has successfully retrieved an access token at this point
var context = new ClientContext(siteUrl);
context.ExecutingWebRequest += (_, e) =>
{
e.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + authResult.AccessToken;
}
context.Load(context.Web);
await context.ExecuteQueryAsync(); // 401 is thrown here
var title = context.Web.Title;
I have tried several different ways of getting around this to no avail:
I have gone to the Admin center of my SharePoint site and given the app FullControl permissions, as well as giving the app those permissions in Azure AD. This doesn't seem to have changed anything, I still get the same 401.
I have registered an entirely new app directly from my SharePoint sub-site admin center and given it FullControl permissions. I used the new client ID and client secret that were generated, and I was able to get back an access token. No luck, still get the 401 calling ClientContext.ExecuteQueryAsync()
I have tried changing my siteUrl to a SharePoint site-specific URL (e.g. https://myorg.sharepoint.com/sites/mySite), but once I do that I am no longer able to retrieve an access token. I instead get an Msal exception thrown, AADSTS500011, which reads:
"The resource principal named https://myorg.sharepoint.com/sites/mysite was not found in the tenant named (my tenant). This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant.
I have also tried using the base siteUrl to retrieve the token, then giving the site-specific URL to ClientContext. I get the same 401 result.
I have tried several different authorities in case the token I'm being provided is invalid. I've tried using the V1 token URL, the V2 token URL, no token-specific URL (only the default authority address + tenant ID). All of these return an access token, but none of them avoid the 401.
A MS documentation article suggests appending an additional "/" to the requested .default scope in instances where a 401 is being returned (e.g. https://myorg.sharepoint.com/sites/mysite//.default). This doesn't seem to have changed anything.
My application seems to have the permissions it needs to do this basic read operation, but I am continually rebuffed. I am using the ClientID, ClientSecret and Tenant ID as copied directly from the AAD application page. The code I'm using above is recommended by Microsoft to use the new SharePointOnline.CSOM package. What am I missing here?
Constructor of ClientContext requires site url including site name.
var clientId = "myClientId";
var clientSecret = "myClientSecret";
var tenantId = "myTenantId";
var authority = "https://login.microsoftonline.com/" + tenantId;
var siteUrl = "https://myorg.sharepoint.com";
var siteName = "MySiteName";
var app = new ConfidentialClientApplicationBuilder
.Create()
.WithClientSecret(clientSecret)
.WithAuthority(authority)
.WithTenantId(tenantId)
.Build();
var paramBuilder = app.AcquireTokenForClient(new[] { siteUrl + "/.default" });
var authResult = await paramBuilder.ExecuteAsync();
// authResult has successfully retrieved an access token at this point
var webFullUrl = $"{siteUrl}/sites/{siteName}";
var context = new ClientContext(webFullUrl);
If the site has some prefix
var webFullUrl = $"{siteUrl}/sites/{sitePrefix}/{siteName}";
I wound up "solving" this problem by using the PnP.Framework NuGet package instead of Microsoft.SharePointOnline.CSOM. I changed nothing else about my app registration or its designated permissions, and PnP.Framework was able to handle it without issue (and with fewer arguments). It seems to know something that SharePointOnline.CSOM doesn't considering that the following simple console app works:
using System;
using PnP.Framework
const string clientId = "myClientId";
const string clientSecret = "myClientSecret";
const string siteUrl = "https://myorg.sharepoint.com/sites/mysite";
using var clientContext = new AuthenticationManager()
.GetACSAppOnlyContext(siteUrl, clientId, clientSecret);
cc.Load(cc.Web);
await cc.ExecuteQueryAsync(); // no longer throws a 401
Console.WriteLine(cc.Web.Title); // prints my site's title
I tried to use the newer PnP.Core SDK, but I couldn't find any documentation or examples on how to get that package working with an app-only client secret authenticated context. PnP.Framework's API is the cleanest and most reliable that I've found as of yet.
I have created web app to CreateOrGet, Delete, Update onlinemeeting using Microsoft Graph API in C#.
To get authorization code as per link Get access on behalf of a user. It returns a webview as HttpClient calls api for AuthCodeGeneration and returns the response, which contains Authcode in browser. I have to manually copy it to execute CreateOrGet, Delete, Update onlinemeeting using Microsoft Graph API.
Is there any way to do this through code in C#?
You don't need to handle the "code" and "access token" by your self.
Install Microsoft Graph .NET SDK and implement Authorization code provider to get the authProvider. Use the authProvider to generate the graphClient.
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithRedirectUri(redirectUri)
.WithClientSecret(clientSecret)
.Build();
AuthorizationCodeProvider authProvider = new AuthorizationCodeProvider(confidentialClientApplication, scopes);
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
var onlineMeeting = new OnlineMeeting
{
StartDateTime = DateTimeOffset.Parse("2019-07-12T21:30:34.2444915+00:00"),
EndDateTime = DateTimeOffset.Parse("2019-07-12T22:00:34.2464912+00:00"),
Subject = "User Token Meeting"
};
await graphClient.Me.OnlineMeetings
.Request()
.AddAsync(onlineMeeting);
I am trying to create online meeting to get the joinurl from Microsoft Team Meeting using Graph SDK but I am getting Forbidden (403) error even after I had provided Application (With Admin Consent) and delegation permission to "OnlineMeetings.Read.All", "OnlineMeetings.Read", "OnlineMeetings.ReadWrite.All", "OnlineMeetings.ReadWrite".
Please see my code below and let me know what I am doing wrong or whether I need to provide any other permissions.
Below is my code :
string[] graphScopes = { "OnlineMeetings.Read.All", "OnlineMeetings.Read",
"OnlineMeetings.ReadWrite.All", "OnlineMeetings.ReadWrite" };
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create("55e5f6cf-****-****-****-4f23d6e****")
.WithTenantId("****b9d4-4dbf-****-888f-21d*563b****")
.WithClientSecret("********************************")
.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;
Create onlineMeeting only requires OnlineMeetings.ReadWrite Delegated Permission.
So it requires user + app authorization rather than app-only authorization.
In this case, you are using Client credentials provider, which means app-only authorization.
You should use Authorization code provider to get the access token, which will include OnlineMeetings.ReadWrite Delegated Permission.
I need to get data from Dynamics CRM 365 Online. Anyone tried this before ?
I need to know what kind of information (clientid, clientsecret) I need, to connect through c sharp and save data (JSON) into a for example a flatfile.
edit:
use ADAL.Net v2 If you need to use the non async method.
Remember to put the Token in the request header under "Authorization".
You need to use OAuth to authenticate to Dynamics 365 Online from your C# code.
// TODO Substitute your correct CRM root service address,
string resource = "https://mydomain.crm.dynamics.com";
// TODO Substitute your app registration values that can be obtained after you
// register the app in Active Directory on the Microsoft Azure portal.
string clientId = "e5cf0024-a66a-4f16-85ce-99ba97a24bb2";
string redirectUrl = "http://localhost/SdkSample";
// Authenticate the registered application with Azure Active Directory.
AuthenticationContext authContext =
new AuthenticationContext("https://login.windows.net/common", false);
AuthenticationResult result = authContext.AcquireToken(resource, clientId, new Uri(redirectUrl));
You can then use the AuthenticationResult to make HTTP requests with HttpClient:
using (HttpClient httpClient = new HttpClient())
{
httpClient.Timeout = new TimeSpan(0, 2, 0); // 2 minutes
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", result.AccessToken);
//TODO Implement your WebApi calls
}
These code samples and additional details, including how to register an application with Azure AD, are in this link: https://learn.microsoft.com/en-us/dynamics365/customer-engagement/developer/connect-customer-engagement-web-services-using-oauth
I tried to look for all over internet but couldn't see how I can achieve what I was asked to. Here is my enterprise app which uses Asp.net Identity for form based authentication. I had extended User and Role along with Groups to provide authorization in my code. (note: not using any group/role directives).
Now I was asked to look at possibility of changing code to accommodate Azure Active Directory authentication. I tried reading on how you can register app, send user to Azure site for authentication, get back token etc. However I'm stuck at 'what-afterwards?' I have authenticated user How can I use my existing Asp.net Identity model where user was stored in sql database. How to use this token to relate the existing user.
Moreover, when I change my project to allow Azure AD, it removes Aspnet.Identity package as its not compatible with Azure AD !!
I even tried manually keeping both packages side by side, I got to point where user is sent to authenticate on Azure, diverted back to home page and again to login on Azure AD in never ending loop.
to summarize the question, How can I authenticate user from AAD and keep using existing Roles and groups authorization.
Edit:
I tried creating separate web service which will authenticate user and send JWT token. which works find if I call it directly on browser, however, when I tried to call this service from my web app I get weird error
Application with identifier 'a2d2---------------' was not found in the directory azurewebsites.net
Weird part here is name of directory is 'azurewebsites.net' and not the default directory I'm using.
Update
Here is code which throws error
public async Task<ActionResult> Login(string returnUrl)
{
try
{
// get the access token
AuthenticationContext authContext = new AuthenticationContext(authority, new TokenCache());
var clientCredential = new ClientCredential(clientId, password);
//Error on below line
AuthenticationResult result = await authContext.AcquireTokenAsync(resourceId, clientCredential);
// give it to the server to get a JWT
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
......
try this:
var client = new RestClient("https://login.microsoftonline.com/{tenant-
Id}/oauth2/v2.0/token");
var request = new RestRequest(Method.POST);
request.AddHeader("cache-control", "no-cache");
request.AddHeader("content-type", "application/x-www-form-urlencoded");
request.AddHeader("grant_type", "password");
request.AddParameter("application/x-www-form-urlencoded",
"grant_type=password&client_id={client-Id}&client_secret={client-
secret}&scope={scopeurl}&userName={username}&password={password}",
ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
var json = response.Content;
var JSONObject = JObject.Parse(json);
var token = (string)JSONObject["access_token"];
I had a similar issue so I created an Office 365 owin security plugin. I shared the code on github. It's based on the katana project at codeplex.
You can find the source code at https://github.com/chadwjames/wakizashi.
You will need to register your application here. When registering the application set the call back uri to https://yourdomain/signin-office365
The Application ID is your Client Id and the Password is your Client Secret.
Once you have it registered you can modify the Startup.Auth.cs and add something like this to the ConfigureAuth method.
//setup office 365
var office365Options = new Office365AuthenticationOptions
{
ClientId = ConfigurationManager.AppSettings["ada:ClientId"],
ClientSecret = ConfigurationManager.AppSettings["ada:ClientSecret"],
Provider = new Office365AuthenticationProvider
{
OnAuthenticated = async context =>
{
await
Task.Run(
() => context.Identity.AddClaim(new Claim("Office365AccessToken", context.AccessToken)));
}
},
SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie
};
office365Options.Scope.Add("offline_access");
app.UseOffice365Authentication(office365Options);
When I have more time I hope to create a nuget package for this.