using System; using System.IO; using System.Linq; using System.Reflection; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using Microsoft.Identity.Client; using Microsoft.Identity.Client.Extensions.Msal; using Microsoft.InformationProtection; namespace AipGateway.AIP { public class AuthDelegateImplementation : IAuthDelegate { public int LastErrNo { get; set; } public string LastErrMsg { get; set; } private readonly AipConfig _aipConfig; private readonly IConfidentialClientApplication _confidentialApp = null; private readonly IPublicClientApplication _publicApp = null; public AuthDelegateImplementation(AipConfig aipConfig) { _aipConfig = aipConfig; LastErrNo = 0; LastErrMsg = String.Empty; var storageProperties = new StorageCreationPropertiesBuilder( "AIPGateway.Cache", Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)) .Build(); var cacheHelper = MsalCacheHelper.CreateAsync(storageProperties).GetAwaiter().GetResult(); if (_aipConfig.LoginType == AipAuthLoginType.authLoginPassword) { _confidentialApp = ConfidentialClientApplicationBuilder.Create(_aipConfig.ClientId) .WithClientSecret(_aipConfig.SecretValue) // .WithAuthority($"https://login.microsoftonline.com/{_aipConfig.TenantId}") // .WithCacheOptions(CacheOptions.EnableSharedCacheOptions) .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient") .Build(); cacheHelper.RegisterCache(_confidentialApp.UserTokenCache); } else if (_aipConfig.LoginType == AipAuthLoginType.authLoginCert) { var authority = $"https://login.windows.net/{_aipConfig.TenantId}"; // var certificate = Utilities.ReadCertificateFromStore(_aipConfig.CertThumbPrint); var myCertificate = X509Certificate2.CreateFromCertFile(_aipConfig.CertThumbPrint); X509Certificate2 certificate = new X509Certificate2(myCertificate); _confidentialApp = ConfidentialClientApplicationBuilder.Create(_aipConfig.ClientId) .WithCertificate(certificate) .WithAuthority(new Uri(authority)) .Build(); cacheHelper.RegisterCache(_confidentialApp.UserTokenCache); } else { _publicApp = PublicClientApplicationBuilder.Create(_aipConfig.ClientId) .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient") .WithAuthority(AzureCloudInstance.AzurePublic, _aipConfig.TenantId) .Build(); cacheHelper.RegisterCache(_publicApp.UserTokenCache); } } 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); // var app = PublicClientApplicationBuilder.Create(_aipConfig.ClientId) // .WithAuthority(authority) // .Build(); // // var scopes = new[] { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" }; // try // { // var accounts = app.GetAccountsAsync().GetAwaiter().GetResult(); // var result = app.AcquireTokenSilent(scopes, accounts.FirstOrDefault()) // .ExecuteAsync().Result; // // Console.WriteLine("AuthDelegateImplementation::AcquireToken ==> AcquireTokenSilent Succeed."); // return result.AccessToken; // } // catch (MsalUiRequiredException) // { // Console.WriteLine("AuthDelegateImplementation::AcquireToken ==> AcquireTokenSilent Failed."); // } 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) // // .WithAuthority($"https://login.microsoftonline.com/{_aipConfig.TenantId}") // // .WithCacheOptions(CacheOptions.EnableSharedCacheOptions) // .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient") // .Build(); // Create confidential client using client secret. // var app = ConfidentialClientApplicationBuilder.Create(_aipConfig.ClientId) // .WithRedirectUri(resource) // .WithAuthority(authority) // .WithClientSecret(_aipConfig.SecretValue) // .Build(); var scopes = new[] { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" }; //app.AddInMemoryTokenCache(); try { var accounts = (_confidentialApp.GetAccountsAsync()).GetAwaiter().GetResult(); result = _confidentialApp.AcquireTokenSilent(scopes, accounts.FirstOrDefault()) .ExecuteAsync().GetAwaiter().GetResult(); Console.WriteLine("AuthDelegateImplementation::AcquireTokenByPassword ==> AcquireTokenSilent Succeed."); return result.AccessToken; } catch (MsalUiRequiredException) { Console.WriteLine("AuthDelegateImplementation::AcquireTokenByPassword ==> AcquireTokenSilent Failed."); try { result = _confidentialApp.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 = Utilities.ReadCertificateFromStore(_aipConfig.CertThumbPrint); var myCertificate = X509Certificate2.CreateFromCertFile(_aipConfig.CertThumbPrint); X509Certificate2 certificate = new X509Certificate2(myCertificate); // Use cert to build ClientAssertionCertificate // var app = ConfidentialClientApplicationBuilder.Create(_aipConfig.ClientId) // .WithCertificate(certificate) // .WithAuthority(new Uri(authority)) // .Build(); // 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 = _confidentialApp.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, _aipConfig.TenantId) // .Build(); var accounts = (_publicApp.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 = _publicApp.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; } // public X509Certificate2 LoadCertificate(string certificateName) // { // string vaultUrl = ""; // string clientId = _aipConfig.ClientId; // string tenantId = _aipConfig.TenantId; // string secret = ""; // // Console.WriteLine($"Loading certificate '{certificateName}' from Azure Key Vault"); // // var credentials = new ClientSecretCredential(tenantId: tenantId, clientId: clientId, clientSecret: secret); // var certClient = new CertificateClient(new Uri(vaultUrl), credentials); // var secretClient = new SecretClient(new Uri(vaultUrl), credentials); // // var cert = GetCertificateAsync(certClient, secretClient, certificateName); // // Console.WriteLine("Certificate loaded"); // return cert; // } // private static X509Certificate2 GetCertificateAsync(CertificateClient certificateClient, // SecretClient secretClient, // string certificateName) // { // // KeyVaultCertificateWithPolicy certificate = certificateClient.GetCertificate(certificateName); // // // Return a certificate with only the public key if the private key is not exportable. // if (certificate.Policy?.Exportable != true) // { // return new X509Certificate2(certificate.Cer); // } // // // Parse the secret ID and version to retrieve the private key. // string[] segments = certificate.SecretId.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries); // if (segments.Length != 3) // { // throw new InvalidOperationException($"Number of segments is incorrect: {segments.Length}, URI: {certificate.SecretId}"); // } // // string secretName = segments[1]; // string secretVersion = segments[2]; // // KeyVaultSecret secret = secretClient.GetSecret(secretName, secretVersion); // // // For PEM, you'll need to extract the base64-encoded message body. // // .NET 5.0 preview introduces the System.Security.Cryptography.PemEncoding class to make this easier. // if ("application/x-pkcs12".Equals(secret.Properties.ContentType, StringComparison.InvariantCultureIgnoreCase)) // { // byte[] pfx = Convert.FromBase64String(secret.Value); // return new X509Certificate2(pfx); // } // // throw new NotSupportedException($"Only PKCS#12 is supported. Found Content-Type: {secret.Properties.ContentType}"); // } // } private 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); // } } }