How can I resolve an unauthorized error when using Azure Management API? - azure

How can I resolve an unauthorized error when using Azure Management API?
Note:
I would prefer to resolve this programmatically (in code) instead of running commands/scripts.
Objective:
I need to retrieve function names from a Function App in Azure.
Example:
var current = Pulumi.Azure.Core.GetClientConfig.InvokeAsync().Result;
var subscriptionId = current.SubscriptionId;
var appName = functionApp.Name;
var url = $"GET https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{appName}/functions?api-version=2022-03-01";
var httpClient = new HttpClient();
var result = await httpClient.GetAsync(url);
if (!result.IsSuccessStatusCode) throw new Exception($"Error: Failed to retrive Azure function names from {appName}");
var json = result.Content.ReadAsStringAsync();
Thoughts:
I think I need to create a bearer token but do not know the steps required.

I tried to reproduce the same in my environment via Postman and got same error as below:
GET https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{appName}/functions?api-version=2022-03-01
To resolve the error, you need to generate bearer token for the service principal and include it in headers section with Authorization parameter.
I registered one Azure AD application in my tenant like this: Go to Azure Portal -> Azure Active Directory -> App registrations -> New registration
Now, create one client secret in that application and copy its value like below:
Make sure to assign proper role based on your requirement. I assigned Reader role to the above service principal under my subscription like below:
Go to Azure Portal -> Subscriptions -> Your Subscription -> Access control (IAM) -> Add role assignment
In my function app, I created one HTTP function named SriHTTP like below:
Now, I generated access token via Postman with below parameters:
POST https://login.microsoftonline.com/<tenantID>/oauth2/v2.0/token
grant_type:client_credentials
client_id: <appID>
client_secret: <secret_value>
scope: https://management.azure.com/.default
Response:
I got the results successfully when I used the above token to call management API like below:
GET https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{appName}/functions?api-version=2022-03-01
Authorization: Bearer <token>
Response:

Related

MS Subcriptions REST API returns empty value

I register a new app to my Azure tenant and then use the Subscription REST API below to get my subscription id. But, it returns an empty value.
Is this a bug of the REST API, or the app is missing some required configurations?
https://learn.microsoft.com/en-us/rest/api/resources/subscriptions/list?tabs=HTTP
I tried to reproduce the same in my environment and got below results:
I registered one Azure AD application and granted API permission like below:
I generated the access token via Postman using below parameters:
POST https://login.microsoftonline.com/<tenantID>/oauth2/v2.0/token
grant_type:client_credentials
client_id:<appID>
client_secret:<secret>
scope: https://management.azure.com/.default
Response:
When I used the above token to get subscriptions, I got same response as you like below:
GET https://management.azure.com/subscriptions?api-version=2020-01-01
Authorization: Bearer <token>
Response:
To get the desired results, make sure to assign required role like Reader to the service principal under your subscription like below:
Go to Azure Portal -> Subscriptions -> Your Subscription -> Access control (IAM) -> Add role assignment
Now I generated token again and got the subscription details like ID successfully with below API call:
GET https://management.azure.com/subscriptions?api-version=2020-01-01
Authorization: Bearer <token>
Response:
If you want to list all subscriptions, then assign Reader role to your service principal under management group level instead of specific subscription.

Azure Active Directory v2 - Get Custom Scope Token

I am learning about generating a token for an OAuth service and it will be used in a chatbot. When I use the following code displayed below, I can get a default scope Graph Token successfully, and this token is valid for MS Graph API calls. Now, what I am trying to achieve is generating a custom scope token in a similar way in order to call an external service(Not MS Graph API). This token needs to have a custom scope. I tried to change the dictionary parameter "scope" to the name of my scope configured for a chatbot in Azure but it fails:
private async Task<string> GetGraphTokenAsync()
{
var dict = new Dictionary<string, string>();
dict.Add("client_id", _graphTokenSettings.ClientId);
dict.Add("client_secret", _graphTokenSettings.ClientSecret);
dict.Add("scope", "https://graph.microsoft.com/.default");
dict.Add("grant_type", "client_credentials");
string gUrl = $"https://login.microsoftonline.com/{_graphTokenSettings.Tenant}/oauth2/v2.0/token";
var client = new HttpClient();
var req = new HttpRequestMessage(HttpMethod.Post, gUrl) { Content = new FormUrlEncodedContent(dict) };
var httpResponseFromService = await client.SendAsync(req);
httpResponseFromService.EnsureSuccessStatusCode();
if (httpResponseFromService.Content is object
&& httpResponseFromService.Content.Headers.ContentType.MediaType == "application/json")
{
string stringFromservice = await httpResponseFromService.Content.ReadAsStringAsync();
JObject tokenresponse = JsonConvert.DeserializeObject<JObject>(stringFromservice);
string token = tokenresponse["access_token"].Value<string>();
return token;
}
else
{
_logger.LogError($"Cannot get token for Microsoft Graph. httpResponseFromService.Content:{httpResponseFromService.Content}" );
throw new Exception("Cannot get token for Microsoft Graph.");
}
}
The provider configuration in my Bot is the following, is it using as Service Provider: Azure Active Directory v2:
This is an example of a custom token generated with an OAuth tool (tenant id and other values changed to just illustrate the data, but all these values match and are correct when working with them), it is calling to the same url "login.microsoftonline.com" that I am trying to call to generate the custom scope token:
This generated custom scope token works. It has been configured at my Tenant Azure level as "api://botid-GUID/access_as_user" but I would like to generate it via http client as my code example. Would you know how can I get a token using this custom scope with a similar httpClient approach? It seems the scope parameter that I am sending ("api://botid-GUID/access_as_user") is not correct for client_credentials grant type call:
Default scope:
dict.Add("client_id", _graphTokenSettings.ClientId);
dict.Add("client_secret", _graphTokenSettings.ClientSecret);
dict.Add("scope", "https://graph.microsoft.com/.default");
dict.Add("grant_type", "client_credentials");
Replaced by:
dict.Add("client_id", _graphTokenSettings.ClientId);
dict.Add("client_secret", _graphTokenSettings.ClientSecret);
dict.Add("scope", "api://botid-GUID/access_as_user");
dict.Add("grant_type", "client_credentials");
Any help will be very appreciated.
I tried to reproduce the same in my environment and got below results:
I have one Azure AD application where I created one custom scope by exposing the API like below:
I registered another application named ClientApp and added above custom scope by granting consent like below:
In my Azure Bot, I added one connection setting with Service Provider as Azure Active Directory v2 like below:
When I ran Test connection, I got the token successfully like below:
When I decoded the above token, I got claims with scope as below:
When you create custom scope by exposing an API, it comes under Delegated permissions that involves user interaction like below:
Note that, client credential flow only works with Application
permissions that does not involve user interaction.
You need to create App role instead of exposing the API in the application with different unique value access-as-user like below:
You can add above App role to your client application that comes under Application permissions and make sure to grant consent as below:
In addition to that, client credentials grant type supports scope that ends with only /.default while using v2 endpoint. Otherwise, it will throw exception like below:
To resolve the above error, you need to replace scope with /.default at end like below while generating token:
POST https://login.microsoftonline.com/<tenantID>/oauth2/v2.0/token
client_id:appID
grant_type:client_credentials
client_secret:secret
scope: api://87xxxa-6xex-4dxa-9xaf-b1dxxxx9819/.default
Response:
When I decoded the above token, I got claims with roles as below:
Note that, decoded token contains Application permissions in roles claim whereas Delegated permissions in scp claim.
In your scenario, if you want to use custom scope with client credentials grant type, you need to create App role with unique value that comes under Application permissions.
Make sure to change scope with /.default at end.

Get bearer token with MSAL.NET to access App Service with EasyAuth

I have an Azure App Service which is authenticated using Azure AD EasyAuth.
I am trying to send a request from another App Service using C# and MSAL.NET (Microsoft.Identity.Client).
The authentication code looks like this
var app = ConfidentialClientApplicationBuilder
.Create(config.ClientId) // The Client ID in the App Registration connected to the App Service
.WithClientSecret(config.ClientSecret)
.WithAuthority(new Uri(config.Authority)) // https://login.microsoftonline.com/tenant.onmicrosoft.com/v2.0
.WithTenantId(config.TenantId) // Tenant Id Guid
.Build();
// Used Scopes: ["https://graph.microsoft.com/.default"]
var credentials = await app.AcquireTokenForClient(config.Scopes)
.ExecuteAsync(cancellationToken);
I get a bearer token successfully, but when I try to call the App Service with token injected to the headers I get a 401 and You do not have permission to view this directory or page. :(
Update 1:
I tried #Jim Xu answer and it's still giving me 401. It returns a www-authenticate header with the following value
The resource id is the same ClientId in the App Reg
Update 2 - Solution
So to summarize the fix:
The requested scopes when calling AcquireTokenForClient should include {Application ID Uri}/.default
In EasyAuth configuration, the Allowed Token Audiences needs to be set to the Application ID Uri as well
If you want to call the Azure API app which enables easy auth, please refer to the following steps
Get the Application ID URI of the AD application you use to enable easy auth
a. In the Azure portal menu, select Azure Active Directory or search for and select Azure Active Directory from any page.
b. Select App registrations > Owned applications > View all applications in this directory. Select your web app name, and then select Overview.
code
var app = ConfidentialClientApplicationBuilder
.Create(config.ClientId) // The Client ID in the App Registration connected to the App Service
.WithClientSecret(config.ClientSecret)
.WithAuthority(new Uri(config.Authority)) // https://login.microsoftonline.com/tenant.onmicrosoft.com/v2.0
.WithTenantId(config.TenantId) // Tenant Id Guid
.Build();
// Used Scopes: ["{Application ID URI}/.default"]
var credentials = await app.AcquireTokenForClient("{Application ID URI}/.default")
.ExecuteAsync(cancellationToken);
For more details, please refer to here.

OAUTH / Azure Functions: Method to auth AAD user for endpoints that don't support service principals

I've been leveraging Azure Function Apps to automate items in Azure. I currently have working functions that connect to Microsoft Graph, Resource Explorer, KV etc. using service principal / OAUTH client credentials flow (inside the function app). To call my function app, I've implemented implicit flow. While I'm not an expert at OAUTH, I am familiar enough now to get this configured and working.
However, there are Azure endpoints I need to use that don't support using a service principal token, they only support an actual AAD user requesting a token. Here's one that I want to run: Create synchronizationJob
If you look at the permissions section of the above link, you'll see that "application" is not supported. I did test this in a function: I can run these endpoints in Graph Explorer fine (as myself), but they fail in the function when using a token linked to a service principal.
Since this new automation is going to be an Azure Function (and not an interactive user), I can't use the authorization code flow. I need this service account's OAUTH to be non-interactive.
TL;DR
I can run the above endpoint in Azure's Graph Explorer just fine:
Azure Graph Explorer
since I'm authenticating as myself, and have a token generated based on my user ID. But for automating using Azure Functions where I need to use this endpoint (which doesn't support OAUTH using an SP), I need some way to have a back-end AAD user auth and pull a token that can be used to run the endpoint.
Any help is welcome! Feel free to tell me that I'm either missing something very basic, or not understanding a core principal here.
As juunas mentioned no guarantee that will work though, I test in my side and it seems doesn't work although I assigned "Global administrator" role to the service principal.
For your situation, you can request the access token in your function code and then use the access token to request the graph api.
Add the code like below in your function to get access token.
HttpClient client = new HttpClient();
var values = new Dictionary<string, string>
{
{ "client_id", "<your app client id>" },
{ "scope", "<scope>" },
{ "username", "<your user name>" },
{ "password", "<your password>" },
{ "grant_type", "password" },
};
var content = new FormUrlEncodedContent(values);
var response = await client.PostAsync("https://login.microsoftonline.com/<your tenant id>/oauth2/v2.0/token", content);
var responseString = await response.Content.ReadAsStringAsync();
var obj = JObject.Parse(responseString);
var accessToken = (string)obj["access_token"];
And then use the access token got above to request graph api.

Authenticate Azure Function by Token with Resource

I have an Azure Function with Authorization/authnetication enabled via AD log in.
I am trying to authenticate by generating a token using client_credentials:
Refer to the following code below:
var tokenendpoint = "https://login.microsoftonline.com/172f05a2-f956-4856-b4c8-9580a54dbd56/oauth2/token";
string clientID = "eaeff78a-26ef-4bcb-b977-638316ff15b7";
string clientSecret = "HvVlipQkpuezmD4YiUcWVpZ5Cn1cP3vxiW61pSpDo8k=";
string resource = "eaeff78a-26ef-4bcb-b977-638316ff15b7"; //ClientID
string grantType = "client_credentials";
using (var reqToken = new WebClient())
{
NameValueCollection parameters = new NameValueCollection();
parameters.Add("client_id", clientID);
parameters.Add("client_secret", clientSecret);
parameters.Add("resource", resource);
parameters.Add("grant_type", grantType);
var responseTokenBytes = reqToken.UploadValues(tokenendpoint, "POST", parameters);
string responseTokenContent = Encoding.UTF8.GetString(responseTokenBytes).Replace(#"\", "");
azureFunctionTokenResponse = responseTokenContent.Deserialize<AzureFunctionTokenResponseBase>();
AzureFunctionToken = azureFunctionTokenResponse.access_token;
}
All works fine if I set the resource as the ClientID of my function.
However, in many examples online the Resource is set to the Azure Function Uri.
If I set my Resource to https://www.xxxxxx.azurewebsites.com then I get a 401 error.
Why is this?
I spent a whole day in getting this to finally work but nowhere in the docs does it say to enter the ClientID as the Resource??
If you use the same AAD app to enable Authorization/Authentication for your Azure Function and your client code to acquire the access_token for accessing your Azure Function, you could specify the resource to the Application ID (ClientID) or the App ID URI of your AAD app.
In general, we would use the ClientID as the resource, and App Service Authorization/Authentication would compare the Client ID you configured under Authentication / Authorization > Azure Active Directory Settings with the aud property of the incoming JWT bearer token, you could leverage https://jwt.io/ to decode your token.
However, in many examples online the Resource is set to the Azure Function Uri.
If I set my Resource to https://www.xxxxxx.azurewebsites.com then I get a 401 error.
I assume that those samples may use the App ID URI, you could set the App ID URI to https://www.xxxxxx.azurewebsites.com for your AAD app (Settings > Properties > App ID URI), then you could use App ID URI for the resource parameter.
Note: For this approach, you may need adjust the Azure Active Directory Settings for your Azure Function, you may keep the Client ID to the Application ID of your AAD app and add App ID URI to ALLOWED TOKEN AUDIENCES list or you could just replace it with your App ID URI.
Additionally, you could ADAL library for acquiring the token. Also, if you create each AAD app for your Azure Function and your client app, you could follow this issue.

Resources