Exchange EWS with delegate, debugger stops after getting authenticationresult - c#

public static void Main(string[] args)
{
TicketSync ts = new TicketSync();
ts.ScanMailBox();
}
private async void ScanMailBox()
{
if (string.IsNullOrWhiteSpace(_mailboxToOpen)) return;
ExchangeService service = new ExchangeService();
try
{
// Using Microsoft.Identity.Client 4.22.0
// Configure the MSAL client to get tokens
var pcaOptions = new PublicClientApplicationOptions
{
ClientId = "*clientGuid*",
TenantId = "*TenantGuid*"
};
var pca = PublicClientApplicationBuilder
.CreateWithApplicationOptions(pcaOptions).Build();
// The permission scope required for EWS access
var ewsScopes = new string[] { "https://outlook.office365.com/EWS.AccessAsUser.All" };
NetworkCredential credentials = new NetworkCredential("***#***.eu", "*******");
AuthenticationResult authResult = await pca.AcquireTokenByUsernamePassword(ewsScopes, credentials.UserName, credentials.SecurePassword).ExecuteAsync();
when i get to that last line my debugger just stops running, no error, even though there is a try catch around it all, even tried adding a try catch around that line solely, but nothing works. Any idea why this is happening?
application is an automated service that would have to check a mailbox periodically using set credentials. i followed these solutions so far:
https://learn.microsoft.com/en-us/exchange/client-developer/exchange-web-services/how-to-authenticate-an-ews-application-by-using-oauth
How can I access a mailbox with restricted permissions through EWS without interactive login?

okay, i figured it out myself. I added this to the code:
var task = pca.AcquireTokenByUsernamePassword(ewsScopes, credentials.UserName, credentials.SecurePassword).ExecuteAsync();
while(!task.IsCompleted)
{
task.Wait(10000);
}
var authResult = task.Result;
this gave me the actual error at first, altho the website stated admin consent was not necessary for user access, it was necessary. hooray.
after fixing this error it worked. So the wait was necessary for it to work and to show the error

Related

Error 403: Forbidden when I try to connect to server OAuth 2.0

I am working in a booking app which was working since Microsoft decided to erase the old connection method (EWS API connection without OAuth) to retrieve info about an Outlook calendar.
I have what is shown below, copied from Microsoft forums:
protected async Task<ExchangeService> exchangeService(string roomEmail)
{
var cca = ConfidentialClientApplicationBuilder
.Create(ConfigurationManager.AppSettings["appId"])
.WithClientSecret(ConfigurationManager.AppSettings["clientSecret"])
.WithTenantId(ConfigurationManager.AppSettings["tenantId"])
.Build();
var ewsScopes = new string[] { "https://outlook.office365.com/.default" };
try
{
var authResult = await cca.AcquireTokenForClient(ewsScopes)
.ExecuteAsync().ConfigureAwait(continueOnCapturedContext: false);
// Configure the ExchangeService with the access token
var ewsClient = new ExchangeService();
ewsClient.Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx");
ewsClient.Credentials = new OAuthCredentials(authResult.AccessToken);
ewsClient.ImpersonatedUserId =
new ImpersonatedUserId(ConnectingIdType.SmtpAddress, roomEmail);
//Include x-anchormailbox header
ewsClient.HttpHeaders.Add("X-AnchorMailbox", roomEmail);
return ewsClient;
}
catch (Exception e)
{
connecting.Text = e.ToString();
loadingScreen(true, false, false, false, true, true, false, true);
return null;
}
}
I've followed all the steps but the result is an "Error 403: forbidden".
As far as I know, this happens when the account you are requesting to doesn't have permissions to access the services, but all the accounts I want to retrieve calendars from have permissions in the AAD.
What can I do? I am blocked and I don't know how to solve this problem, help will be appreciated
Thank you.
When you are using confidential client then make sure you have given the permission with "Application" type and not delegated.
"full_access_as_app" this permission will give the access to mailbox and it will need admin approval first then only it will be available to use. In fact all application level permission will need admin approval.

Getting error while using DataFactoryManagementClient.pipelines method

I am trying to automate ADF pipeline runs from .NET. Was able to create a client and when I try to do the below call
var pipeline = client.Pipelines.Get(resourceGroup, dataFactoryName, "test_1");
I am getting the error as Microsoft.Rest.Azure.CloudException: 'The document could not be retrieved because it does not exist.
Complete code below:
// Authenticate and create a data factory management client
IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create(applicationId)
.WithAuthority("https://login.microsoftonline.com/" + tenantID)
.WithClientSecret(authenticationKey)
.WithLegacyCacheCompatibility(false)
.WithCacheOptions(CacheOptions.EnableSharedCacheOptions)
.Build();
AuthenticationResult result = await app.AcquireTokenForClient(new string[] { "https://management.azure.com//.default" }).ExecuteAsync();
ServiceClientCredentials cred = new TokenCredentials(result.AccessToken);
DataFactoryManagementClient client = new DataFactoryManagementClient(cred)
{
SubscriptionId = subscriptionId
};
Loos like it was a silly mistake. pipeline, I was trying to access from here was deleted by another process and hence this call failed.

Authorization_IdentityNotFound accessing Microsoft Graph from daemon application

I am developing a daemon application. I have downloaded the sample application to try to see if it would work, but get the same error from that app too. My admin has checked as much as possible on his end, revealing nothing. The desired end result is to have a program that can send emails on behalf of users and read mail from certain mailboxes. I also need to be able to confirm in a quick way that my application is configured correctly.
This program will not have user interactivity and will be operating on behalf of the company.
What I've done:
Created app registration at aad.portal.azure.com.
Created a client secret.
Admin has given consent for 11 Microsoft Graph permissions: Mail.Read (Application), Mail.ReadBasic (Application), Mail.ReadBasic.All (Application), Mail.ReadWrite (Application), Mail.Send (Application), User.Export.All (Application), User.Invite.All (Application), User.ManageIdentities.All (Application), User.Read (Delegated), User.Read.All (Application), User.ReadWrite.All (Application)
Integration Assistant (preview) shows all green except for 1 item, which is "Use certificate credentials instead of password credentials (client secrets).". This particular one is acceptable to me.
On the Authentication page, this is not treated as a public client.
Nuget packages used:
Microsoft.Identity.Client 4.13.0
Microsoft.Graph 3.5.0
My sandbox code:
async void Main()
{
var graphFacade = new MsGraphFacade();
Console.WriteLine(await graphFacade.ValidateCredentialsAsync());
}
class MsGraphFacade
{
private static async Task<GraphServiceClient> GetGraphApiClient()
{
var clientId = "(Redacted)";
var secret = "(Redacted)";
var app = ConfidentialClientApplicationBuilder
.CreateWithApplicationOptions(new ConfidentialClientApplicationOptions{
ClientId = clientId,
ClientSecret = secret,
AadAuthorityAudience = AadAuthorityAudience.AzureAdMultipleOrgs,
})
.Build();
Console.WriteLine("Getting token");
var token = await app
.AcquireTokenForClient(new[] { "https://graph.microsoft.com/.default" })
.ExecuteAsync();
Console.WriteLine("Got token");
var accessToken = token.AccessToken;
var graphServiceClient = new GraphServiceClient(
new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage
.Headers
.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
return Task.CompletedTask;
}));
Console.WriteLine("New client returned.");
return graphServiceClient;
}
public async Task<bool> ValidateCredentialsAsync()
{
try
{
Console.WriteLine("Attempting something simple");
var client = await GetGraphApiClient();
var user = await client.Users
.Request()
.Top(1)
.Select(x => x.DisplayName)
.GetAsync();
if (user != null)
{
return true;
}
return false;
}
catch (Exception e)
{
Console.WriteLine("2");
Console.WriteLine(e);
return false;
}
}
}
Output of code:
Attempting something simple
Getting token
Got token
New client returned.
2
Code: Authorization_IdentityNotFound Message: The identity of the calling application could not be established.
Inner error:
AdditionalData:
request-id: f31bc340-1cdf-485f-b852-f1e2822201ef
date: 2020-05-15T20:24:38
False
Any ideas of what to check next or tweak will be greatly appreciated.
Thanks in advance for any help.
I'm guessing the issue is you have not specified the target tenant.
You've defined it like this:
AadAuthorityAudience = AadAuthorityAudience.AzureAdMultipleOrgs
You need to instead specify Azure public cloud + tenant guid. I'm on my phone right now so I can't look up the exact syntax :/

How do you use AcquireTokenSilentAsync to authenticate a user?

I am attempting to programmatically authorise an Azure application from an Azure AD joined machine.
If I go to the application URL in Internet Explorer it is able to verify the logged on user account.
My current code looks something like this:
using Microsoft.IdentityModel.Clients.ActiveDirectory;
AuthenticationContext context = new AuthenticationContext("https://login.microsoftonline.com/TENANTGUID");
Uri uri = new Uri("urn:ietf:wg:oauth:2.0:oob");
var pparams = new PlatformParameters(PromptBehavior.Auto, null);
AuthenticationResult result = await context.AcquireTokenAsync("https://graph.windows.net", "1950a258-227b-4e31-a9cf-717495945fc2", uri, pparams);
This call is successful but I want to acquire a token for the currently logged on user.
The first two parameters to the AcquireTokenAsync call are resource and clientid.
I can get the Homepage url and application id for the application I want to access but cannot find a combination of the two that works.
What parameters should I pass to this function to silently validate the logged on user and obtain an authorisation header that can be used in subsequent calls to the application?
I'd advise you now MSAL.NET Integrated Windows Authentication for domain or AAD joined machines:
the code would be something like :
static async Task GetATokenForGraph()
{
string tenant = "contoso.com" // can also be a GUID or organizations for multi-tenant
string authority = $"https://login.microsoftonline.com/{tenant}";
string[] scopes = new string[] { "user.read" };
PublicClientApplication app = new PublicClientApplication(clientId, authority);
var accounts = await app.GetAccountsAsync();
AuthenticationResult result=null;
if (accounts.Any())
{
result = await app.AcquireTokenSilentAsync(scopes, accounts.FirstOrDefault());
}
else
{
try
{
result = await app.AcquireTokenByIntegratedWindowsAuthAsync(scopes);
}
catch (MsalUiRequiredException ex)
{
// For details see the article

How to issue access token based on Windows Authentication with Identity Server 4

My goal is to protect a Web API, such that it can only be accessed by a client using an access token issued by IS based on Windows authentication. I worked through this basic sample:
http://docs.identityserver.io/en/release/quickstarts/1_client_credentials.html
Now, I need to extend the basic sample such that the access token returned to the client is issued based on Windows authentication. More specifically, I need to have the user (which is executing the client application) to be authenticated against Active Directory when requesting an access token. How should this be done?
I have already been running the quick start (https://github.com/IdentityServer/IdentityServer4.Templates) successfully, where the login is based on a Windows external provider, but I cannot figure out how to adopt this functionality to my strategy.
I tried using an Extension Grant (http://docs.identityserver.io/en/release/topics/extension_grants.html) and have the ValidateAsync() method be the one to do the authentication against AD, but could not make it work (primarily since HttpContext is not available). Is this even the correct approach?
Update
In this system, the client is a console application (without human interaction), thus the context is the account running the application.
I have been running the QuickstartUI and see how the AccountController logic handles the "Windows" button, but cannot grasp how to combine this with requesting access tokens. My client code goes like this:
static async Task Main(string[] args)
{
var disco = await DiscoveryClient.GetAsync("http://localhost:50010");
var tokenClient = new TokenClient(disco.TokenEndpoint);
var tokenResponse = await tokenClient.RequestCustomGrantAsync("CustomWindows"); // Not sure about this
var client = new HttpClient();
client.SetBearerToken(tokenResponse.AccessToken);
var response = await client.GetAsync("http://localhost:50011/api/identity");
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(JArray.Parse(content));
Console.ReadLine();
}
I am not sure how to use the TokenClient to get an access token in this case. I would prefer not to store and use passwords, but have IS issue access tokens based on authenciating the client context against AD. If implicit or hybrid flows must be used in this case, how must that be done?
I had the same requirement and implemented it using an extension grant.
This is the code of the extension grant:
public class WinAuthGrantValidator : IExtensionGrantValidator
{
private readonly HttpContext httpContext;
public string GrantType => WinAuthConstants.GrantType;
public WinAuthGrantValidator(IHttpContextAccessor httpContextAccessor)
{
httpContext = httpContextAccessor.HttpContext;
}
public async Task ValidateAsync(ExtensionGrantValidationContext context)
{
// see if windows auth has already been requested and succeeded
var result = await httpContext.AuthenticateAsync(WinAuthConstants.WindowsAuthenticationSchemeName);
if (result?.Principal is WindowsPrincipal wp)
{
context.Result = new GrantValidationResult(wp.Identity.Name, GrantType, wp.Claims);
}
else
{
// trigger windows auth
await httpContext.ChallengeAsync(WinAuthConstants.WindowsAuthenticationSchemeName);
context.Result = new GrantValidationResult { IsError = false, Error = null, Subject = null };
}
}
}
And this is the client code:
var httpHandler = new HttpClientHandler
{
UseDefaultCredentials = true,
};
// request token
var tokenClient = new TokenClient(disco.TokenEndpoint, "client", "secret", httpHandler, AuthenticationStyle.PostValues);
var tokenResponse = await tokenClient.RequestCustomGrantAsync("windows_auth", "api1");

Categories