using System; using System.Linq; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using Microsoft.Identity.Client; using Microsoft.InformationProtection; namespace AipGateway.AIP { public class AuthDelegateImplementation : IAuthDelegate { public int LastErrNo { get; set; } public string LastErrMsg { get; set; } private readonly AipConfig _aipConfig; public AuthDelegateImplementation(AipConfig aipConfig) { _aipConfig = aipConfig; LastErrNo = 0; LastErrMsg = String.Empty; } public void ResetError() { LastErrNo = 0; LastErrMsg = string.Empty; } private void SetError(int errNo, string errMsg1, string errMsg2 = "No Exception Message.") { LastErrNo = errNo; LastErrMsg = errMsg1 + "\r\n" + errMsg2; Console.WriteLine("AuthDelegateImplementation::SetError ==> {0}, {1}, {2}", errNo, errMsg1, errMsg2); } public string AcquireToken(Identity identity, string authority, string resource, string claim) { Console.WriteLine("AuthDelegateImplementation::AcquireToken ==> LoginType: {0}", _aipConfig.LoginType); if (_aipConfig.LoginType == AipAuthLoginType.authLoginPassword) { return AcquireTokenByPassword(identity, authority, resource, claim); } if (_aipConfig.LoginType == AipAuthLoginType.authLoginCert) { return AcquireTokenByCert(identity, authority, resource, claim); } return AcquireTokenById(identity, authority, resource, claim); } private string AcquireTokenByPassword(Identity identity, string authority, string resource, string claims) { Console.WriteLine("AuthDelegateImplementation::AcquireTokenByPassword"); AuthenticationResult result; var authorityUri = new Uri(authority); authority = $"https://{authorityUri.Host}/{_aipConfig.TenantId}"; var app = ConfidentialClientApplicationBuilder.Create(_aipConfig.ClientId) .WithClientSecret(_aipConfig.SecretValue) .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient") .Build(); var scopes = new[] { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" }; //app.AddInMemoryTokenCache(); try { result = app.AcquireTokenForClient(scopes) .WithTenantId(_aipConfig.TenantId) .ExecuteAsync() .GetAwaiter() .GetResult(); } catch (MsalServiceException ex) when (ex.Message.Contains("AADSTS70011")) { // Invalid scope. The scope has to be of the form "https://resourceurl/.default" // Mitigation: change the scope to be as expected SetError(1, "AcquireTokenByPassword.Scope provided is not supported.", ex.Message); return null; } return result.AccessToken; } private string AcquireTokenByCert(Identity identity, string authority, string resource, string claims) { Console.WriteLine("AuthDelegateImplementation::AcquireTokenByPassword"); AuthenticationResult result; var authorityUri = new Uri(authority); authority = $"https://{authorityUri.Host}/{_aipConfig.TenantId}"; // Read cert from local machine //var certificate = ReadCertificateFromStore(certThumb); var myCertificate = X509Certificate2.CreateFromCertFile(_aipConfig.CertThumbPrint); X509Certificate2 certificate = new X509Certificate2(myCertificate); // Use cert to build ClientAssertionCertificate var app = ConfidentialClientApplicationBuilder.Create(_aipConfig.ClientId) .WithCertificate(certificate) .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient") .Build(); // Append .default to the resource passed in to AcquireToken(). var scopes = new[] { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" }; try { result = app.AcquireTokenForClient(scopes).ExecuteAsync().Result; } catch (MsalServiceException ex) when (ex.Message.Contains("AADSTS70011")) { // Invalid scope. The scope has to be of the form "https://resourceurl/.default" // Mitigation: change the scope to be as expected SetError(1, "AcquireTokenByCert.Scope provided is not supported.", ex.Message); return null; } return result.AccessToken; } private string AcquireTokenById(Identity identity, string authority, string resource, string claims) { Console.WriteLine("AcquireTokenById::AcquireTokenByPassword"); AuthenticationResult result; var authorityUri = new Uri(authority); authority = $"https://{authorityUri.Host}/{_aipConfig.TenantId}"; var app = PublicClientApplicationBuilder.Create(_aipConfig.ClientId) .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient") .WithAuthority(AzureCloudInstance.AzurePublic, "2e58414a-c6ae-43ff-aaf5-45ab8b78a404") .Build(); var accounts = (app.GetAccountsAsync()).GetAwaiter().GetResult(); // Append .default to the resource passed in to AcquireToken(). var scopes = new[] { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" }; try { result = app.AcquireTokenInteractive(scopes) .WithAccount(accounts.FirstOrDefault()) .WithPrompt(Prompt.SelectAccount) .ExecuteAsync() .ConfigureAwait(false) .GetAwaiter() .GetResult(); } catch (Exception ex) { SetError(1, "AcquireTokenById.Login Failed.", ex.Message); return null; } return result.AccessToken; } private static X509Certificate2 ReadCertificateFromStore(string thumbprint) { X509Certificate2 cert = null; X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser); store.Open(OpenFlags.ReadOnly); X509Certificate2Collection certCollection = store.Certificates; // Find unexpired certificates. X509Certificate2Collection currentCerts = certCollection.Find(X509FindType.FindByTimeValid, DateTime.Now, false); // From the collection of unexpired certificates, find the ones with the correct name. X509Certificate2Collection signingCert = currentCerts.Find(X509FindType.FindByThumbprint, thumbprint, false); // Return the first certificate in the collection, has the right name and is current. cert = signingCert.OfType().OrderByDescending(c => c.NotBefore).FirstOrDefault(); store.Close(); return cert; } public async Task AcquireTokenAsync(string authority, string resource, string claims) { AuthenticationResult result = null; if (authority.ToLower().Contains("common")) { var authorityUri = new Uri(authority); authority = String.Format("https://{0}/{1}", authorityUri.Host, _aipConfig.TenantId); } var app = PublicClientApplicationBuilder.Create(_aipConfig.ClientId) .WithAuthority(authority) .WithDefaultRedirectUri() .Build(); var accounts = (app.GetAccountsAsync()).GetAwaiter().GetResult(); // Append .default to the resource passed in to AcquireToken(). string[] scopes = { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" }; try { result = await app.AcquireTokenSilent(new[] { "https://aadrm.com/user_impersonation" }, accounts.FirstOrDefault()).ExecuteAsync(); // result = await _app.AcquireTokenSilent(scopes, // accounts.FirstOrDefault()) // .ExecuteAsync(); } catch (MsalUiRequiredException) { result = app.AcquireTokenInteractive(scopes) .WithAccount(accounts.FirstOrDefault()) .WithPrompt(Prompt.SelectAccount) .ExecuteAsync() .ConfigureAwait(false) .GetAwaiter() .GetResult(); } // Return the token. The token is sent to the resource. return result; } // private async Task GetAccessTokenOnBehalfOfUser(string authority, string resource) // { // IConfidentialClientApplication app; // // if (false) // { // // Read X509 cert from local store and build ClientAssertionCertificate. // X509Certificate2 cert = Utilities.ReadCertificateFromStore(_aipConfig.CertThumbPrint); // // // Create confidential client using certificate. // app = ConfidentialClientApplicationBuilder.Create(_aipConfig.ClientId) // .WithRedirectUri(resource) // .WithAuthority(authority) // .WithCertificate(cert) // .Build(); // } // // else // { // // Create confidential client using client secret. // app = ConfidentialClientApplicationBuilder.Create(_aipConfig.ClientId) // .WithRedirectUri(resource) // .WithAuthority(authority) // .WithClientSecret(_aipConfig.SecretValue) // .Build(); // } // // // Store user access token of authenticated user. // var ci = (ClaimsIdentity)_claimsPrincipal.Identity; // string userAccessToken = (string)ci.BootstrapContext; // // // // Generate a user assertion with the UPN and access token. // UserAssertion userAssertion = new UserAssertion(userAccessToken, "urn:ietf:params:oauth:grant-type:jwt-bearer"); // // // Append .default to the resource passed in to AcquireToken(). // List scopes = new List() { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" }; // // AuthenticationResult result = await app.AcquireTokenOnBehalfOf(scopes, userAssertion) // .ExecuteAsync(); // // // Return the token to the API caller // return (result.AccessToken); // } } }