Authenticate an EWS application by using OAuth within custom class - c#

I have code to authenticate with EWS using oAuth working fine if I call it from winform button click, but not working if I place my code inside custom class and call it inside constructor I don't know what is the problem ?
Authenticate Code function :
public async Task<AuthenticationResult> oAuthLoginRequest()
{
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
var cca = ConfidentialClientApplicationBuilder
.Create(Settings.Default.appId)
.WithClientSecret(Settings.Default.clientSecret)
.WithTenantId(Settings.Default.tenantId)
.Build();
var ewsScopes = new string[] { "https://outlook.office365.com/.default" };
try
{
_authenticationResult = await cca.AcquireTokenForClient(ewsScopes)
.ExecuteAsync();
return _authenticationResult;
}
catch (Exception ex)
{
string.Format("oAuthLoginRequest: Exception= {0}", ex.Message).LogIt(TLogType.ltError);
return _authenticationResult;
}
}
Working well and I got access token :
private async void button1_Click(object sender, EventArgs e)
{
oAuthLoginRequest();
//Access Token Available here
var accessToken = _authenticationResult.AccessToken ; //Working fine
}
NOT WORKING :
public class TServiceController
{
private bool _started = false;
public bool Started { get { return _started; } }
TEWSService mailService = null;
public ExchangeService _service = null;
public AuthenticationResult _authenticationResult = null;
public DateTimeOffset TokenExpiresOn { get; set; }
public async Task<AuthenticationResult> oAuthLoginRequest()
{
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
var cca = ConfidentialClientApplicationBuilder
.Create(Settings.Default.appId)
.WithClientSecret(Settings.Default.clientSecret)
.WithTenantId(Settings.Default.tenantId)
.Build();
// "https://outlook.office365.com/.default" ,"https://outlook.office365.com/EWS.AccessAsUser.All" , "https://graph.microsoft.com/Mail.Send"
// "https://ps.outlook.com/full_access_as_app"
var ewsScopes = new string[] { "https://outlook.office365.com/.default" };
try
{
_authenticationResult = await cca.AcquireTokenForClient(ewsScopes).ExecuteAsync();
TokenExpiresOn = _authenticationResult.ExpiresOn;
("AccessToken:" + _authenticationResult.AccessToken).LogIt(TLogType.ltDebug);
}
catch (Exception ex)
{
string.Format("oAuthLoginRequest: Exception= {0}", ex.Message).LogIt(TLogType.ltError);
}
return _authenticationResult;
}
public TServiceController()
{
var auth = oAuthLoginRequest().Result; //STUCK HERE
"Service controller started.".LogIt();
} //end constructore
} //END CLASS
Any explanation ?
I tried two methods one of them work just fine in winform click button and other solution not working within my class constructor .

Related

C# Microsoft Graph: Contacts: ErrorAccessDenied

A while ago I created an application to create Outlook events in the calendars of our employees with Microsoft Graph in a background process. Now I want to expand the current application and also manage contacts.
First of all I want to created a new contact person.
Code that calls the GraphHelper.cs:
AuthenticationConfig config = AuthenticationConfig.ReadFromJsonFile("appsettings.json");
string[] scopes = new string[] { $"{config.ApiUrl}.default" };
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create($"{config.ClientId}")
.WithTenantId($"{config.Tenant}")
.WithAuthority(new Uri(config.Authority))
.WithClientSecret($"{config.ClientSecret}")
.Build();
ClientCredentialProvider authenticationProvider = new ClientCredentialProvider(confidentialClientApplication, scopes[0]);
GraphHelper.Initialize(authenticationProvider);
string status = CreateContact().GetAwaiter().GetResult();
private static async Task<string> CreateContact()
{
return await GraphHelper.CreateContact("admin#premed.be");
}
GraphHelper.cs:
public static void Initialize(IAuthenticationProvider authProvider)
{
graphClient = new GraphServiceClient(authProvider);
}
public static async Task<string> CreateContact(string userName)
{
var contact = new Contact
{
GivenName = "GivenNameTest",
Surname = "SurnameTest",
EmailAddresses = new List<EmailAddress>()
{
new EmailAddress
{
Address = "GivenNameTest.SurnameTest#hotmail.com",
Name = "GivenNameTest SurnameTest Test"
}
},
BusinessPhones = new List<String>()
{
"+32489789654"
}
};
try
{
await graphClient.Users[userName]
.Contacts
.Request()
.AddAsync(contact);
return "OK";
}
catch (ServiceException ex)
{
return ex.Message;
}
}
When I run the code, I get an ErrorAccessDenied error "Access is denied. Check credentials and try again."
But I don't understand why I get the error. For the autorisation I use the same functionality I used for the calendar. Also the same tenant, client-id and client secret is used. In the Azure portal all permissions are given.
For example: creating an event is no problem.
public static async Task<string> CreateEvent(Event newEvent, string userName)
{
try
{
// POST /users/{id | userPrincipalName}/events
var returnEvent = await graphClient.Users[userName]
.Events
.Request()
.Header("Prefer", "outlook.timezone=\"Europe/Paris\"")
.AddAsync(newEvent);
return returnEvent.Id;
}
catch (ServiceException ex)
{
return ex.Message;
}
}
Can someone help me please?

AcquireTokenSilentAsync never using tokens from the cache

I have a class that acts as a wrapper for the MS Graph SDK. Fairly simple purpose, inside the class there are methods for getting various data sets out of Graph for a particular user.
EDIT: this runs under the context of an application, so no user creds are ever used.
All of that part works fine, what isn't working is the DelegateAuthenticationProvider never finds the access token in the cache. Each call to a graph endpoint gets a new token, even in the same instance of the class. Within the class I'm using a singleton pattern for the GraphServiceClient.
Here is the code I'm using to handle the client:
private static GraphServiceClient _graphServiceClient;
private static AuthenticationContext _authContext;
private static readonly object _locker = new();
private GraphServiceClient GetClient(M365ServiceOptions options)
{
if (_graphServiceClient == null)
{
lock (_locker)
{
if (_graphServiceClient == null)
{
_authContext = new AuthenticationContext($"https://login.microsoftonline.com/{options.TenantId}/");
var provider = new DelegateAuthenticationProvider(async (requestMessage) =>
{
AuthenticationResult accessToken;
try
{
//Use Token from cache or refresh token
accessToken = await _authContext.AcquireTokenSilentAsync(options.GraphURL, options.ClientId);
_logger.LogDebug("Cache Hit");
}
catch (AdalSilentTokenAcquisitionException)
{
//If no cached token, get a new one
_logger.LogDebug($"Cache Miss: {_authContext.TokenCache?.Count}");
var credentials = new ClientCredential(options.ClientId, options.ClientSecret);
accessToken = _authContext.AcquireTokenAsync(options.GraphURL, credentials).Result;
}
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.AccessToken);
});
_graphServiceClient = new GraphServiceClient(provider);
}
}
}
return _graphServiceClient;
}
While debugging it is clear the token cache has an item in it, and the details all seem to match, but no matter what, the AcquireTokenSilentAsync always throws the AdalSilentTokenAcquisitionException exception and forces it to get a new token for each call. This is impacting performance as no matter what, each call to the graph gets a new token.
Thank you for any assistance.
Please try this class. First call AuthenticationHelper.GetAuthenticatedClient() to get the a GraphServiceClient, then you this to access the user information.
public class AuthenticationHelper
{
static readonly string clientId = "";
public static string[] Scopes = { "User.Read" };
public static PublicClientApplication IdentityClientApp = new PublicClientApplication(clientId);
public static string TokenForUser = null;
public static DateTimeOffset Expiration;
private static GraphServiceClient graphClient = null;
// Get an access token for the given context and resourced. An attempt is first made to
// acquire the token silently. If that fails, then we try to acquire the token by prompting the user.
public static GraphServiceClient GetAuthenticatedClient()
{
if (graphClient == null)
{
// Create Microsoft Graph client.
try
{
graphClient = new GraphServiceClient(
"https://graph.microsoft.com/v1.0",
new DelegateAuthenticationProvider(
async (requestMessage) =>
{
var token = await GetTokenForUserAsync();
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
requestMessage.Headers.Add("SampleID", "MSGraphConsoleApp");
}));
return graphClient;
}
catch (Exception ex)
{
Debug.WriteLine("Could not create a graph client: " + ex.Message);
}
}
return graphClient;
}
public static async Task<string> GetTokenForUserAsync()
{
AuthenticationResult authResult;
try
{
authResult = await IdentityClientApp.AcquireTokenSilentAsync(Scopes, IdentityClientApp.GetAccountsAsync().Result.First());
TokenForUser = authResult.AccessToken;
}
catch (Exception)
{
if (TokenForUser == null || Expiration <= DateTimeOffset.UtcNow.AddMinutes(5))
{
authResult = await IdentityClientApp.AcquireTokenAsync(Scopes);
TokenForUser = authResult.AccessToken;
Expiration = authResult.ExpiresOn;
}
}
return TokenForUser;
}
public static void SignOut()
{
foreach (var user in IdentityClientApp.GetAccountsAsync().Result)
{
IdentityClientApp.RemoveAsync(user);
}
graphClient = null;
TokenForUser = null;
}
}

Close .Auth/login/done after MSAL login successful in Xamarin application

I have been setting up AD(MSAL) Authentication using azure with my application but am having an issue closing the window that appears after successful sign in. The page that appears within the embedded browser with the link returning to my API homepage simply states "You have successfully signed in" with a link below to return to previous page with goes to my API home page.
The below is my code in my App.xaml.cs
public partial class App : Application
{
public static IPublicClientApplication PCA = null;
public static string ClientID = "********-****-****-****-**********";
public static string[] Scopes = { "User.Read" };
public static string Username = string.Empty;
public static object ParentWindow { get; set; }
public App()
{
InitializeComponent();
}
protected override async void OnStart()
{
PCA = PublicClientApplicationBuilder.Create(ClientID)
//.WithRedirectUri($"msal{ClientID}://auth")
.WithRedirectUri("https://kpiapp-api-dev.azurewebsites.net/.auth/login/aad/callback")
.WithIosKeychainSecurityGroup("com.microsoft.adalcache")
.WithAuthority(AzureCloudInstance.AzurePublic, "********-****-****-****-**********") //TenantID
.Build();
MainPage = new NavigationPage(new LoginPage());
}
protected override void OnSleep()
{
// Handle when your app sleeps
}
protected override void OnResume()
{
// Handle when your app resumes
}
}
And my Loginpage.xaml.cs:
public partial class LoginPage : ContentPage
{
public LoginPage()
{
InitializeComponent();
}
async void OnSignIn(object sender, EventArgs e)
{
AuthenticationResult authResult = null;
IEnumerable<IAccount> accounts = await App.PCA.GetAccountsAsync();
var current = Connectivity.NetworkAccess;
bool connectionFound = false;
if (current == NetworkAccess.Internet)
{
connectionFound = true;
}
string APIData = "";
if(connectionFound == true)
{
try
{
if (SignInButton.Text == "Sign in")
{
try
{
IAccount firstAccount = accounts.FirstOrDefault();
authResult = await App.PCA.AcquireTokenSilent(App.Scopes, firstAccount)
.ExecuteAsync();
}
catch (MsalUiRequiredException ex)
{
try
{
authResult = await App.PCA.AcquireTokenInteractive(App.Scopes)
.WithParentActivityOrWindow(App.ParentWindow)
.ExecuteAsync();
}
catch (Exception ex2)
{
await DisplayAlert("Acquire token interactive failed. See exception message for details: ", ex2.Message, "Dismiss");
}
}
if (authResult != null)
{
var content = await GetHttpContentWithTokenAsync(authResult.AccessToken);
SignInButton.Text = "Sign out";
}
}
else
{
while (accounts.Any())
{
await App.PCA.RemoveAsync(accounts.FirstOrDefault());
accounts = await App.PCA.GetAccountsAsync();
}
});
SignInButton.Text = "Sign in";
}
}
catch (Exception ex)
{
await DisplayAlert("Authentication failed. See exception message for details: ", ex.Message, "Dismiss");
}
await Task.Yield();
APIData = getAPIData();
}
else
{
await DisplayAlert("Connection Error", "Check your internet connection and try again", "Try again");
}
if (APIData != "ConnectionError")
{
await Navigation.PushAsync(new MainPage(APIData));
}
else
{
await Task.Delay(500);
await DisplayAlert("API Download error", "Error connecting to API", "Try again");
}
//MainPage = new MainPage(APIData);
}
public async Task<string> GetHttpContentWithTokenAsync(string token)
{
try
{
//get data from API
HttpClient client = new HttpClient();
HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/me");
message.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
HttpResponseMessage response = await client.SendAsync(message);
string responseString = await response.Content.ReadAsStringAsync();
return responseString;
}
catch (Exception ex)
{
await DisplayAlert("API call to graph failed: ", ex.Message, "Dismiss");
return ex.ToString();
}
}
private string getAPIData()
{
string APIData = "";
try
{
APIData = new WebClient().DownloadString("****/api/data");
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
APIData = "ConnectionError";
}
return APIData;
}
}
I'm aware this is doing nothing with that sign in and won't access the api data at the moment. I'm really just looking to get the authentication window closed and then work from there.
Thanks
I managed to solve this issue, by adding .WithUseEmbeddedWebView(true) to the second call of authResult so it appears like this:
catch (MsalUiRequiredException ex)
{
try
{
authResult = await App.PCA.AcquireTokenInteractive(App.Scopes)
.WithParentActivityOrWindow(App.ParentWindow)
.WithUseEmbeddedWebView(true)
.ExecuteAsync();
}
catch (Exception ex2)
{
await DisplayAlert("Acquire token interactive failed. See exception message for details: ", ex2.Message, "Dismiss");
}
}

client certificate with NSUrlSessionDelegate in xamarin

I want to implement client certificate authentication in my xamarin app.
On top of that I am using a custom Certificate Authority (CA) and TLS 1.2.
Until now I managed to get it running using android, UWP and WPF. The only platform missing is ios.
Here is my NSUrlSessionDelegate:
public class SSLSessionDelegate : NSUrlSessionDelegate, INSUrlSessionDelegate
{
private NSUrlCredential Credential { get; set; }
private SecIdentity identity = null;
private X509Certificate2 ClientCertificate = null;
private readonly SecCertificate CACertificate = null;
public SSLSessionDelegate(byte[] caCert) : base()
{
if (caCert != null)
{
CACertificate = new SecCertificate(new X509Certificate2(caCert));
}
}
public void SetClientCertificate(byte[] pkcs12, char[] password)
{
if (pkcs12 != null)
{
ClientCertificate = new X509Certificate2(pkcs12, new string(password));
identity = SecIdentity.Import(ClientCertificate);
SecCertificate certificate = new SecCertificate(ClientCertificate);
SecCertificate[] certificates = { certificate };
Credential = NSUrlCredential.FromIdentityCertificatesPersistance(identity, certificates, NSUrlCredentialPersistence.ForSession);
}
else
{
ClientCertificate = null;
identity = null;
Credential = null;
}
}
public override void DidReceiveChallenge(NSUrlSession session, NSUrlAuthenticationChallenge challenge, Action<NSUrlSessionAuthChallengeDisposition, NSUrlCredential> completionHandler)
{
if (challenge.ProtectionSpace.AuthenticationMethod == NSUrlProtectionSpace.AuthenticationMethodClientCertificate)
{
NSUrlCredential c = Credential;
if (c != null)
{
completionHandler.Invoke(NSUrlSessionAuthChallengeDisposition.UseCredential, c);
return;
}
}
if (challenge.ProtectionSpace.AuthenticationMethod == NSUrlProtectionSpace.AuthenticationMethodServerTrust)
{
SecTrust secTrust = challenge.ProtectionSpace.ServerSecTrust;
secTrust.SetAnchorCertificates(new SecCertificate[] {
CACertificate
});
secTrust.SetAnchorCertificatesOnly(true);
}
completionHandler.Invoke(NSUrlSessionAuthChallengeDisposition.PerformDefaultHandling, null);
}
}
This works if no client certificate is configured DidReceiveChallenge is called once with AuthenticationMethodServerTrust and the custom CA is accepted.
But as soon as a client certificate is configured DidReceiveChallenge gets called 4 times (twice for each AuthenticationMethod) and I am getting NSURLErrorDomain (-1200) error.
Anyone any idea what I am doing wrong?
Update
The SSLSessionDelegate is used like this:
public class HttpsServer : AbstractRemoteServer, IRemoteServer
{
private static readonly Logger LOG = LogManager.GetLogger();
private SSLSessionDelegate sSLSessionDelegate;
private NSUrlSession session;
private NSUrl baseAddress;
public HttpsServer()
{
sSLSessionDelegate = new SSLSessionDelegate(SSLSupport.GetTruststoreRaw());
NSUrlSessionConfiguration configuration = NSUrlSessionConfiguration.DefaultSessionConfiguration;
configuration.HttpShouldSetCookies = true;
configuration.TimeoutIntervalForRequest = 30;
configuration.TLSMinimumSupportedProtocol = SslProtocol.Tls_1_2;
configuration.TimeoutIntervalForResource = 30;
NSMutableDictionary requestHeaders;
if (configuration.HttpAdditionalHeaders != null)
{
requestHeaders = (NSMutableDictionary)configuration.HttpAdditionalHeaders.MutableCopy();
}
else
{
requestHeaders = new NSMutableDictionary();
}
AppendHeaders(requestHeaders, SSLSupport.GetDefaultHeaders());
configuration.HttpAdditionalHeaders = requestHeaders;
session = NSUrlSession.FromConfiguration(configuration, (INSUrlSessionDelegate)sSLSessionDelegate, NSOperationQueue.MainQueue);
baseAddress = NSUrl.FromString(SSLSupport.GetBaseAddress());
}
public void SetClientCertificate(byte[] pkcs12, char[] password)
{
sSLSessionDelegate.SetClientCertificate(pkcs12, password);
}
public override async Task<string> GetString(string url, Dictionary<string, string> headers, CancellationToken cancellationToken)
{
NSData responseContent = await GetRaw(url, headers, cancellationToken);
return NSString.FromData(responseContent, NSStringEncoding.UTF8).ToString();
}
private async Task<NSData> GetRaw(string url, Dictionary<string, string> headers, CancellationToken cancellationToken)
{
NSMutableUrlRequest request = GetRequest(url);
request.HttpMethod = "GET";
request.Headers = AppendHeaders(request.Headers, headers);
Task<NSUrlSessionDataTaskRequest> taskRequest = session.CreateDataTaskAsync(request, out NSUrlSessionDataTask task);
cancellationToken.Register(() =>
{
if (task != null)
{
task.Cancel();
}
});
try
{
task.Resume();
NSUrlSessionDataTaskRequest taskResponse = await taskRequest;
if (taskResponse == null || taskResponse.Response == null)
{
throw new Exception(task.Error.Description);
}
else
{
NSHttpUrlResponse httpResponse = (NSHttpUrlResponse)taskResponse.Response;
if (httpResponse.StatusCode == 303)
{
if (!httpResponse.AllHeaderFields.TryGetValue(new NSString("Location"), out NSObject locationValue))
{
throw new Exception("redirect received without Location-header!");
}
return await GetRaw(locationValue.ToString(), headers, cancellationToken);
}
if (httpResponse.StatusCode != 200)
{
throw new Exception("unsupported statuscode: " + httpResponse.Description);
}
return taskResponse.Data;
}
}
catch (Exception ex)
{
throw new Exception("communication exception: " + ex.Message);
}
}
}
And here my Info.plist
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>XXXXXXXXXX</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
Update 2
Neither I found the solution nor could anyone give me a hint, so I finally dropped client-certificates for now. I switched to OAuth2 for authorization and use my own certificate-authority (no self-signed certificate) for server -authentication which works well.
But still I am interested in this issue and glad for every idea in how to make it work.
I would suggest using ModernHttpClient. It supports ClientCertificates for Android and iOS.
It's opensource so you could always check out their github for reference if you want to finish your own implementation.
ModernHttpClient

Xamarin Forms http async request

i am trying to make an asynchronous call to a webservice.
I would like to make this call when opening the app (App.xaml.cs).
According to the answer that comes back to me, it has to navigate to a particular page
But I do not work.
public partial class App : PrismApplication
{
public App(IPlatformInitializer initializer = null) : base(initializer) { }
protected override void OnInitialized()
{
InitializeComponent();
try
{
CheckLogin().Wait();
}
catch (Exception e)
{
var t = e;
}
}
private static async Task CheckLogin()
{
try
{
var login = new Login
{
Email = "test#test.com",
Password = "test",
};
var client = new HttpClient { BaseAddress = new Uri("http://www.api.com/test/") };
var data = JsonConvert.SerializeObject(login);
var content = new StringContent(data, Encoding.UTF8, "application/json");
var response = await client.PostAsync(#"api/it-IT/auth/token", content); //crash without error, freeze
if (response.IsSuccessStatusCode)
{
var successResult = JsonConvert.DeserializeObject<HttpResponseMessage>(response.Content.ReadAsStringAsync().Result);
if (successResult != null)
{
//return true;
}
else
{
//return false;
}
}
}
catch (Exception e)
{
var t = e;
}
}
protected override void RegisterTypes()
{
Container.RegisterTypeForNavigation<NavigationPage>();
Container.RegisterTypeForNavigation<MainPage>();
Container.RegisterTypeForNavigation<MainPage2>();
Container.RegisterTypeForNavigation<MainPage3>();
}
}
When does the postasync call does not go more forward, not I get no errors, but does not proceed.
But if I try the same code in an application console, everything works fine, why?
class Program
{
static void Main(string[] args)
{
Console.WriteLine("A");
CheckLogin().Wait();
Console.WriteLine("K");
Console.ReadKey();
}
private static async Task CheckLogin()
{
try
{
var login = new Login
{
Email = "test#test.com",
Password = "#test",
};
var client = new HttpClient { BaseAddress = new Uri("http://www.api.com/test/") };
var data = JsonConvert.SerializeObject(login);
var content = new StringContent(data, Encoding.UTF8, "application/json");
var response = await client.PostAsync(#"api/it-IT/auth/token", content);
if (response.IsSuccessStatusCode)
{
}
}
catch (Exception e)
{
var t = e;
}
}
}
If I try to do the same operation within a command with wait I do not work the same error, but if I do await, it will work fine, but in App.xaml.cs in OnInitialized() I can not put await
public DelegateCommand callCommand { get; set; }
public MainPage2ViewModel()
{
callCommand = new DelegateCommand(Call);
}
private void Call()
{
//await CheckLogin(); // work
CheckLogin().Wait(); // not work the same problem
var i = "pippo";
}
private async Task CheckLogin()
{
....
}
Is there anything to set with xamarin or with prism?
I've also the same strange error...
i fix with this workaround (use an async void that wrap async task)...
public App()
{
InitializeComponent();
Current.MainPage = new LoadingPage();
}
protected override void OnStart()
{
MagicInit();
base.OnStart();
}
public static async void MagicInit()
{
var f = await FileSystem.Current.LocalStorage.CreateFileAsync("db.sqlite", CreationCollisionOption.OpenIfExists);
DbConnection = f.Path;
await DataService.DbFill();
User = await DataService.Instance.Table<SpUser>().FirstOrDefaultAsync();
Current.MainPage = User != null ? (Page)new MainPage() : new LoginPage();
}

Categories

Resources