Call Azure Function with AD Authorization from a Windows desktop app - azure

GOAL:
I want to have an Azure Functions (HttpTrigger) that I can call through a Windows desktop app. I want the access to the function be controlled by Active Directory and only authorized users to be able to call it.
CURRENT STATUS:
I followed the guide here to create a desktop app, with AD authorization. I also created an Azure Function to which I added an "App Service Authentication" with "Log in with Azure Active Directory" and created a new app registration to handle this. In my desktop app I added a button that calls this function.
PROBLEM:
When I call the function directly through its link in a browser, everything works perfectly; if I am authorized, it calls the function, if I am not I am redirected to a log in screen and after a successful log in (for an authorized user only) I get the result of the function.
The problems come when I try to do this through my desktop app. When I press the function call button, I am redirected to the log in screen and as soon as I successfully log in with my credentials, I get the error:
AADSTS50011: The reply URL specified in the request does not match the reply URLs configured for the application: <app-id>
This happens when in my app registration I do not have an Authentication option for "Mobile and desktop applications", only for "Web".
If I add the "Mobile and desktop applications" option, then the original button (from the tutorial above) can log in and work properly (in the previous case, it is giving me the same error) but this time, when I try to call the function through the button I added, the program is crashing with the errors:
Inner Exception 1:
HttpRequestException: An error occurred while sending the request.
Inner Exception 2:
WebException: The underlying connection was closed: An unexpected error occurred on a send.
Inner Exception 3:
IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host.
Inner Exception 4:
SocketException: An existing connection was forcibly closed by the remote host
If I force the use of TLS 1.2 I get an 401 error: "You do not have permission to view this directory or page.". If I try to call a function that does not use AD authorization, then the whole process is successful. My code:
private async void CallFunctionButton_Click(object sender, RoutedEventArgs e)
{
AuthenticationResult authResult = null;
var app = App.PublicClientApp;
ResultText.Text = string.Empty;
TokenInfoText.Text = string.Empty;
var accounts = await app.GetAccountsAsync();
var firstAccount = accounts.FirstOrDefault();
try
{
authResult = await app.AcquireTokenSilent(scopes, firstAccount)
.ExecuteAsync();
}
catch (MsalUiRequiredException ex)
{
System.Diagnostics.Debug.WriteLine($"MsalUiRequiredException: {ex.Message}");
try
{
authResult = await app.AcquireTokenInteractive(scopes)
.WithAccount(accounts.FirstOrDefault())
.WithParentActivityOrWindow(new WindowInteropHelper(this).Handle)
.WithPrompt(Prompt.SelectAccount)
.ExecuteAsync();
}
catch (MsalException msalex)
{
ResultText.Text = $"Error Acquiring Token:{System.Environment.NewLine}{msalex}";
}
}
catch (Exception ex)
{
ResultText.Text = $"Error Acquiring Token Silently:{System.Environment.NewLine}{ex}";
return;
}
if (authResult != null)
{
this.SignOutButton.Visibility = Visibility.Visible;
string token = authResult.AccessToken;
using (var client = new HttpClient())
{
// With an explicit selection of the security protocol the program does not crash.
// Instead it gives 401 Unauthorized error, when already signed in.
// Without the following line, the program crashes.
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
string requestUrl = $"the_URL_of_my_function";
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
HttpResponseMessage response = client.SendAsync(request).Result;
var responseString = response.Content.ReadAsStringAsync().Result;
ResultText.Text = responseString;
DisplayBasicTokenInfo(authResult);
}
}
}
QUESTION:
Can I call/use an Azure Function that needs authorization through a Windows desktop app and how?

Regarding the issue, it may relate to the TLS version. As fae as I knew, at the moment, Azure App Service will be created with TLS 1.2 by default. But, WPF application uses TLS 1.0 by default. So we cannot call the Azure function. Regarding how to fix it, please refer to the document
Update
Regarding how to call the Azure function projected by Azure AD, please refer to the following steps
Configure Azure AD for Azure Function
Create a client application in Azure AD
Configure API permissions and get the scope we nedd
code
string[] scopes = new string[]
{"https://testfun08.azurewebsites.net/user_impersonation" };// the scope you copy
private async void CallFunctionButton_Click(object sender, RoutedEventArgs e)
{
// get token
AuthenticationResult authResult = null;
var app = App.PublicClientApp;
ResultText.Text = string.Empty;
TokenInfoText.Text = string.Empty;
var accounts = await app.GetAccountsAsync();
var firstAccount = accounts.FirstOrDefault();
try
{
authResult = await app.AcquireTokenSilent(scopes, firstAccount)
.ExecuteAsync();
}
catch (MsalUiRequiredException ex)
{
System.Diagnostics.Debug.WriteLine($"MsalUiRequiredException: {ex.Message}");
try
{
authResult = await app.AcquireTokenInteractive(scopes)
.WithAccount(accounts.FirstOrDefault())
.WithParentActivityOrWindow(new WindowInteropHelper(this).Handle)
.WithPrompt(Prompt.SelectAccount)
.ExecuteAsync();
}
catch (MsalException msalex)
{
ResultText.Text = $"Error Acquiring Token:{System.Environment.NewLine}{msalex}";
}
}
catch (Exception ex)
{
ResultText.Text = $"Error Acquiring Token Silently:{System.Environment.NewLine}{ex}";
return;
}
//call Azure function
if (authResult != null)
{
this.SignOutButton.Visibility = Visibility.Visible;
string token = authResult.AccessToken;
using (var client = new HttpClient())
{
// Without the following line, the program crashes.
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
string requestUrl = $"the_URL_of_my_function";
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
HttpResponseMessage response = client.SendAsync(request).Result;
var responseString = response.Content.ReadAsStringAsync().Result;
ResultText.Text = responseString;
DisplayBasicTokenInfo(authResult);
}
}
}

Related

How to let `Invoke Azure Function` Task fail from Funkction App

I have an Azure Function App which is triggered by the task Invoke Azure Function in DevOps Pipeline, because the Function App is taking so long I´m using the async mode in the function and the task is configured in callback mode. That all is working fine. The issue I´m having is that when an exception is occouring in my function app, I want to send a callback to my pipeline so it fails. So it doesn´t stay in waiting for response mode. Is there a way to fail a DevOps Pipeline with a callback?
I have been using this documentation from Microsoft.
Here is some code I tried:
var body = JsonConvert.SerializeObject(new
{
status = "Cancelling",
name = "Taskfailed",
taskId = taskInstanceId.ToString(),
jobId = jobId.ToString(),
result = "failed", // also tryed fail
}) ;
PostEvent(callbackUrl, body, authToken)
The function which is sending the callback
public static void PostEvent(String callbackUrl, String body, String authToken)
{
try
{
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
var requestContent = new StringContent(body, Encoding.UTF8, "application/json");
var response = client.PostAsync(new Uri(callbackUrl), requestContent).Result;
var responseContent = response.Content.ReadAsStringAsync().Result;
log.LogInformation(response.StatusCode.ToString());
log.LogInformation(responseContent);
}
catch (Exception ex)
{
log.LogInformation("failed to cancel pipeline");
}
}

SharePoint Online Issue with GetAuthenticationCookie returning "Value cannot be null. Parameter name: cookieHeader"

I was authenticating SharePoint Online users via an API interface. For last couple of years it has been working fine. But Since Monday i am getting error
Value cannot be null. Parameter name: cookieHeader
There is a code to generate HTTPClientHandler for SharePoint Online in API that is hosted on Azure.
HttpClientHandler result = new HttpClientHandler();
try
{
SecureString securePassword = new SecureString();
foreach (char c in userPassword) { securePassword.AppendChar(c); }
SharePointOnlineCredentials credentials = new SharePointOnlineCredentials(userName, securePassword);
result.Credentials = credentials;
string authCookieValue = credentials.GetAuthenticationCookie(new Uri(hostWebURL));
result.CookieContainer.SetCookies(new Uri(hostWebURL), authCookieValue);
}
catch (Exception ex)
{
throw ex;
}
return result;
In the above line of code, we are getting null value of ‘authCookieValue’.
I also checked the value of ‘LegacyAuthProtocolsEnabled’ through SharePoint Online Management Shell using below command, it was already true.
$TenantSettings = Get-SPOTenant
$TenantSettings.LegacyAuthProtocolsEnabled
I started getting the same error at some point. It was clearly a change on the Sharepoint Online side, because nothing had changed on my side for years.
I think the key line that fixed it for me was:
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
See below:
public async Task<List<SharepointDocumentDetail>> GetFileInfoFromSharepoint(string specificFolderUrl)
{
// Once I added this, the error went away
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
List<SharepointDocumentDetail> ret = new List<SharepointDocumentDetail>();
var restUrl = string.Format("{0}/_api/web/GetFolderByServerRelativeUrl('{1}')/Files?$expand=Files", this.Sharepoint_DocRootUrl, specificFolderUrl);
//Creating Credentials
var passWord = new SecureString();
foreach (var c in this.Sharepoint_Password) passWord.AppendChar(c);
var credential = new SharePointOnlineCredentials(this.Sharepoint_User, passWord);
using (var handler = new HttpClientHandler() { Credentials = credential })
{
Uri uri = new Uri(this.Sharepoint_DocRootUrl);
// I was getting the error on this line
handler.CookieContainer.SetCookies(uri, credential.GetAuthenticationCookie(uri));
...
...
...

Azure AD Multi-tenant Applications - Azure Resource Management API - Get List of Subscriptions for all Tenants obtained using Tenant List API

Trying to get all azure subscriptions for all the tenants' user is associated, I am using an azure sample solution which I got from GitHub but the solution is not able to get all the subscription.
Sample Solution Link.
When looping through each tenant I am getting error after calling Azure Resource Management List Subscription API
The Below Try Code block throughs exception for all tenants other than tenant in claims
Failed to acquire token silently. Call method AcquireToken
string tenantId = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
and the exception is handled in the catch block where authContext.AcquireToken is called instead of authContext.AcquireTokenSilent
public static List<Subscription> GetUserSubscriptions(string organizationId)
{
List<Subscription> subscriptions = null;
//string tenantId = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
string signedInUserUniqueName = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Name).Value.Split('#')[ClaimsPrincipal.Current.FindFirst(ClaimTypes.Name).Value.Split('#').Length - 1];
try
{
// Aquire Access Token to call Azure Resource Manager
ClientCredential credential = new ClientCredential(ConfigurationManager.AppSettings["ida:ClientID"],
ConfigurationManager.AppSettings["ida:Password"]);
// initialize AuthenticationContext with the token cache of the currently signed in user, as kept in the app's EF DB
AuthenticationContext authContext = new AuthenticationContext(
string.Format(ConfigurationManager.AppSettings["ida:Authority"], organizationId),
new ADALTokenCache(signedInUserUniqueName));
AuthenticationResult result =
authContext.AcquireTokenSilent(
ConfigurationManager.AppSettings["ida:AzureResourceManagerIdentifier"], credential,
new UserIdentifier(signedInUserUniqueName, UserIdentifierType.RequiredDisplayableId));
subscriptions = new List<Subscription>();
// Get subscriptions to which the user has some kind of access
string requestUrl = string.Format("{0}/subscriptions?api-version={1}",
ConfigurationManager.AppSettings["ida:AzureResourceManagerUrl"],
ConfigurationManager.AppSettings["ida:AzureResourceManagerAPIVersion"]);
// Make the GET request
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = client.SendAsync(request).Result;
if (response.IsSuccessStatusCode)
{
string responseContent = response.Content.ReadAsStringAsync().Result;
dynamic subscriptionsResult = (Json.Decode(responseContent)).value;
foreach (dynamic subscription in subscriptionsResult)
{
subscriptions.Add(new Subscription()
{
Id = subscription.subscriptionId,
DisplayName = subscription.displayName,
OrganizationId = organizationId
});
}
}
}
below catch block is able to get the token after changing the tenant id(organisation Id) dynamically but getting the below error when using the token with REST API
{"error":{"code":"InvalidAuthenticationToken","message":"The received access token is not valid: at least one of the claims 'puid' or 'altsecid' or 'oid' should be present. If you are accessing as application please make sure service principal is properly created in the tenant."}}
catch (AdalException ex)
{
if (ex.ErrorCode == "failed_to_acquire_token_silently")
{
ClientCredential credential = new ClientCredential(ConfigurationManager.AppSettings["ida:ClientID"],
ConfigurationManager.AppSettings["ida:Password"]);
// initialize AuthenticationContext with the token cache of the currently signed in user, as kept in the app's EF DB
AuthenticationContext authContext = new AuthenticationContext(
string.Format(ConfigurationManager.AppSettings["ida:Authority"], organizationId), new ADALTokenCache(signedInUserUniqueName));
//List<TokenCacheItem> items = authContext.TokenCache.ReadItems().ToList();
string resource = ConfigurationManager.AppSettings["ida:AzureResourceManagerIdentifier"];
AuthenticationResult result = authContext.AcquireToken(resource, credential);
subscriptions = new List<Subscription>();
// Get subscriptions to which the user has some kind of access
string requestUrl = string.Format("{0}/subscriptions?api-version={1}", ConfigurationManager.AppSettings["ida:AzureResourceManagerUrl"],
ConfigurationManager.AppSettings["ida:AzureResourceManagerAPIVersion"]);
// Make the GET request
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = client.SendAsync(request).Result;
if (response.IsSuccessStatusCode)
{
string responseContent = response.Content.ReadAsStringAsync().Result;
var subscriptionsResult = (Json.Decode(responseContent)).value;
foreach (var subscription in subscriptionsResult)
subscriptions.Add(new Subscription()
{
Id = subscription.subscriptionId,
DisplayName = subscription.displayName,
OrganizationId = organizationId
});
}
else
{
string errMessage = response.Content.ReadAsStringAsync().Result;
}
}
}
return subscriptions;
}```

RequestJWTUserToken method occasionally seems to be returning wrong AccessToken

I'm using RequestJWTUserToken (docusign.esign.dll file version 4.1.1.0) to generate access tokens for a web application for each user, however occasionally the web application is sending documents from the wrong User's account.
I have logged the steps of the application, and when the error occurs, 2 (or more) of the my users are authenticating their UserIDs through our Integrator key at about the exact same time.
Each user is re-authenticating each time it is time to send a document. The logs that I'm writing show that I am sending in the correct GUID in the parameter list to the RequestJWTUserToken method. Any ideas on what could be occurring?
The method snippet is:
private OAuth.OAuthToken UpdateToken(string strUserGuid)
{
try
{
string ServicePointManager_SecurityProtocol_TLS_12 = ConfigurationManager.AppSettings["ServicePointManager_SecurityProtocol_TLS_12"];
System.Collections.Specialized.NameValueCollection obj = ConfigurationManager.AppSettings;
if (ServicePointManager_SecurityProtocol_TLS_12 == null || ServicePointManager_SecurityProtocol_TLS_12.Equals("true", StringComparison.OrdinalIgnoreCase)) ServicePointManager.SecurityProtocol = Tls12;
System.Net.ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
var mtype = ServicePointManager.SecurityProtocol;
OAuth.OAuthToken authToken =
ApiClient.RequestJWTUserToken(IntegratorKey,
strUserGuid,
AuthServer,
PrivateKey,
1);
AccessToken = authToken.access_token;
//set Account Property
if (Account == null) Account = GetAccountInfo();
ApiClient = new ApiClient(Account.BaseUri + "/restapi");
ExpiresIn = DateTime.Now.Second + authToken.expires_in.Value;
return authToken;
}
catch (ApiException ex)
{
if (ex.Source.Contains("DocuSign.eSign") && strUserGuid != "")
{
if (ex.Message.Contains("consent_required")) //send email with consent to sign link
throw new Exception("DocuSign Consent Required for " + HttpContext.Current.Session["USER_FULLNAME"]);
else
switch (ex.ErrorCode)
{
case 400: throw new Exception("Bad Request: The requested UserID may exist in DocuSign, but there is an issue with the Docusign User Account.");
default: throw new Exception("General Error");
}
}
else throw;
}
catch
{
throw;
}
}

How to connect Azure database with Active Directory-Universal with MFA Support .Net Core

I am trying to connect to azure database.
My current connection string
"return $"Password={this.Password}; Persist Security Info=True;User ID = { this.User }; Initial Catalog = { this.Database }; Data Source = { this.Server }";" like this. How can I connect to azure database with Active Directory-Universal with MFA Support
If you want to connect Azure SQL Database with Active Directory-Universal with MFA, you can connect your SQL database with Azure AD access token. For example
1. Register a web application
Configure permissions
Code( I use ADAL to get access token)
static void Main(string[] args)
{
string authory = "https://login.microsoftonline.com/hanxia.onmicrosoft.com";
AuthenticationContext authContext = new AuthenticationContext(authory);
Console.WriteLine("get token");
var result = GetTokenViaCode(authContext).Result;
var connection = new SqlConnection("Data Source=[my database].database.windows.net;Initial Catalog=[my initial catalog];");
connection.AccessToken = result.AccessToken;
connection.Open();
Console.WriteLine();
}
static async Task<AuthenticationResult> GetTokenViaCode(AuthenticationContext ctx)
{
string resource = "https://database.windows.net";
string clientId = "2c4aae8f-392c-419a-b454-8f8c1ff1ec0c";
AuthenticationResult result = null;
try
{
DeviceCodeResult codeResult = await ctx.AcquireDeviceCodeAsync(resource, clientId);
Console.ResetColor();
Console.WriteLine("You need to sign in.");
Console.WriteLine("Message: " + codeResult.Message + "\n");
result = await ctx.AcquireTokenByDeviceCodeAsync(codeResult);
}
catch (Exception exc)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Something went wrong.");
Console.WriteLine("Message: " + exc.Message + "\n");
}
return result;
}
Please note that I test it in console application, so I use device code flow to get access token. If you want to use it in web app, you can use OpenID flow to implememnt it. For more deatils, please refer to the sample.

Resources