Azure Identity Authentication DefaultAzureCredential with Node Js - node.js

I was trying to authenticate to Azure DefaultAzureCredential using #azure/identity in Node js to get the reports of Azure API Management Service.
Things I have done :
Created An API Management Service from Azure Portal
Registered an application with Azure AD and create a service principal using this documentation.
I Have configured environment variables correctly to use DefaultAzureCredential as mentioned in this documentation.
AZURE_TENANT_ID,
AZURE_CLIENT_ID,
AZURE_CLIENT_SECRET,
AZURE_SUBSCRIPTION,
But the authentication is getting failed and I am not able to generate credentials. when I consoled the new DefaultAzureCredential(); response, it says that UnavailableMessage: 'DefaultAzureCredential => failed to retrieve a token from the included credentials',
require("dotenv").config();
const { DefaultAzureCredential } = require("#azure/identity");
const { ApiManagementClient } = require("#azure/arm-apimanagement");
if (!process.env.AZURE_TENANT_ID) throw Error("AZURE_TENANT_ID is missing from environment variables.");
if (!process.env.AZURE_CLIENT_ID) throw Error("AZURE_CLIENT_ID is missing from environment variables.");
if (!process.env.AZURE_CLIENT_SECRET) throw Error("AZURE_CLIENT_SECRET is missing from environment variables.");
if (!process.env.AZURE_RESOURCE_GROUP) throw Error("AZURE_RESOURCE_GROUP is missing from environment variables.");
if (!process.env.AZURE_SERVICE_NAME) throw Error("AZURE_SERVICE_NAME is missing from environment variables.");
if (!process.env.AZURE_SUBSCRIPTION) throw Error("AZURE_SUBSCRIPTION is missing from environment variables.");
const subscriptionId = process.env.AZURE_SUBSCRIPTION;
const credentials = new DefaultAzureCredential();
console.log(credentials);
And I got this Error,
DefaultAzureCredential {
UnavailableMessage: 'DefaultAzureCredential => failed to retrieve a token from the included credentials',
_sources: [
EnvironmentCredential { _credential: [ClientSecretCredential] },
ManagedIdentityCredential {
isEndpointUnavailable: null,
clientId: 'c8xxxxxxxx5ac8',
identityClient: [IdentityClient]
},
AzureCliCredential {},
VisualStudioCodeCredential {
cloudName: 'AzureCloud',
identityClient: [IdentityClient],
tenantId: 'common'
}
]
}
As one of the answer to a similar question in stack overflow mentioned that The DefaultAzureCredential works even though it shows the unavailable message,
I tried moving on to getting reports of an API Management Service using #azure/identity
const client = new ApiManagementClient(credentials, subscriptionId);
const resourceGroupName = process.env.AZURE_RESOURCE_GROUP;
const serviceName = process.env.AZURE_SERVICE_NAME;
const filter = "callCountSuccess";
client.reports
.listBySubscription(
resourceGroupName,
serviceName,
filter
)
.then((result) => {
console.log(JSON.stringify(result));
})
.catch((err) => {
console.log(err);
});
But as this is also giving the 403 error,
response: {
body: `{"error":
{"code":"AuthorizationFailed",
"message":
"The client 'cxxxxxxxxxxxxxxx569' with object id 'cxxxxxxxxxxxxxxx569'
does not have authorization to perform action 'Microsoft.ApiManagement/service/reports/read'
over scope '/subscriptions/85xxxxxxx3c5/resourceGroups/axxxb/providers/Microsoft.ApiManagement/service/Axxxx/reports/bySubscription'
or the scope is invalid.
If access was recently granted, please refresh your credentials."}}`,
headers: HttpHeaders { _headersMap: [Object] },
status: 403
},
EDIT
I have added the API Management Sevice Reader Role to The Api management service but I am getting the same error as above.

The reason you are running into AuthorizationFailed error is because it looks like you have not assigned any permissions (RBAC role) to your Service Principal.
By default, the Service Principal will not have any permissions to perform operations on an Azure Subscription. You will need to grant appropriate permissions explicitly by assigning suitable RBAC role to your Service Principal.
You can try by assigning Reader role to your Service Principal at Subscription, Resource Group or API Management resource level. You may find this link helpful: https://learn.microsoft.com/en-us/azure/role-based-access-control/role-assignments-steps.
Once appropriate role has been assigned, you should not get this error.

Related

Azure AD CustomEvents causing MsalUiRequiredException

using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp()
.EnableTokenAcquisitionToCallDownstreamApi(new string[] { "a" })
.AddDistributedTokenCaches();
IDW10502: An MsalUiRequiredException was thrown due to a challenge for the user. See https://aka.ms/ms-id-web/ca_incremental-consent.
If i remove OpenIdConnectOptions events, i dont see any error. Is this a bug ?
services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = (context) =>
{
if (context.Request.Headers.ContainsKey("X-Forwarded-Host"))
{
context.ProtocolMessage.RedirectUri = "https://" + context.Request.Headers["X-Forwarded-Host"] + Configuration.GetSection("AzureAd").GetValue<String>("CallbackPath");
}
return Task.FromResult(0);
}
};
});
MsalUiRequiredException
Error Code: AADSTS65001: The user or administrator has not consented
to use the application with ID '{appId}' named '{appName}'. Send an
interactive authorization request for this user and resource.
Mitigation
Get user consent first. If you aren't using .NET Core (which doesn't have any Web UI), call (once only) AcquireTokeninteractive. If you are using .NET core or don't want to do an AcquireTokenInteractive, the user can navigate to a URL to give consent: https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id={clientId}&response_type=code&scope=user.read. to call AcquireTokenInteractive: app.AcquireTokenInteractive(scopes).WithAccount(account).WithClaims(ex.Claims).ExecuteAsync();
And also Try with enabling Multi-Factor Authentication on your tenant.
For more details refer this document

#azure/identity node.js cannot authenticate

I have been following every single example I can find on the internet to enable me to authenticate with azure via js / node using an application identity as per the following example:
const account = process.env.ACCOUNT_NAME || '';
// Azure AD Credential information is required to run this sample:
if (
!process.env.AZURE_TENANT_ID ||
!process.env.AZURE_CLIENT_ID ||
!process.env.AZURE_CLIENT_SECRET
) {
console.warn(
'Azure AD authentication information not provided, but it is required to run this sample. Exiting.'
);
return {
success: false,
message:
'Azure AD authentication information not provided, but it is required to run this sample. Exiting.',
};
}
const defaultAzureCredential = new DefaultAzureCredential();
console.log('credential', defaultAzureCredential);
I have all of the env vars in my code and I've checked, double checked and triple checked these are accurate.
When trying to run the code i get this error in the console.log:
credential DefaultAzureCredential {
UnavailableMessage: 'DefaultAzureCredential => failed to retrieve a token from the included credentials',
_sources: [
EnvironmentCredential { _credential: [ClientSecretCredential] },
ManagedIdentityCredential {
isEndpointUnavailable: null,
identityClient: [IdentityClient]
},
ManagedIdentityCredential {
isEndpointUnavailable: null,
clientId: '04e6dd8e-0000-0000-0000-eb9b3eb60e27',
identityClient: [IdentityClient]
},
AzureCliCredential {},
VisualStudioCodeCredential {
cloudName: 'AzureCloud',
identityClient: [IdentityClient],
tenantId: 'common'
}
]
}
I am now completely stuck. I do not want to use shared access tokens due to a requirement to connect to multiple storage accounts (and even use these credentials to create NEW storage accounts going forward.)
Any advice, debugging or suggestions more than welcome....
The DefaultAzureCredential works in your issue, even though it shows the unavailable message.
You could console the EnvironmentCredential, and it will contain the Environment Variables.
Note: If you're just using Environment Variables, I suggest you use EnvironmentCredential.
DefaultAzureCredential and EnvironmentCredential can be configured
with environment variables.
Get secret in key vault using DefaultAzureCredential:

Authorize a user for azure blob access

I am authenticating the users to my web page using the microsoft login via adal-node library.
adal-node has an AuthenticationContext from which we can get a JWT token using acquireTokenWithAuthorizationCode.
So, the users of my active directory app can now successfully login with their Microsoft accounts.
Now, the question is how to get their RBAC roles for a specific storageaccount/container/blob using the above received JWT Token? Is that even possible?
Or should I be using a library like azure-arm-authorization for this purpose? I have set the RBAC roles for each storageaccount/container/blob but I am not finding any online documentation on how to get these roles for every logged in user of my app.
Any help would be invaluable.
TL;DR How do I authorize azure blobs?
According to my research, if we want to use Azure AD Authentication to acess Azure blob storage, we need to assign Azure RABC role for Azure storage account or container. For more details, please refer to here. Besides, we can use Azure CLI to assign role and get role assignment.
For example
# assign role
az role assignment create \
--role "Storage Blob Data Contributor" \
--assignee <email> \
--scope "/subscriptions/<subscription>/resourceGroups/<resource-group>/providers/Microsoft.Storage/storageAccounts/<storage-account>/blobServices/default/containers/<container>"
# list role assignment of the resource
az role assignment list --scope "/subscriptions/<subscription>/resourceGroups/<resource-group>/providers/Microsoft.Storage/storageAccounts/<storage-account>/blobServices/default/containers/<container>"
For further information, please read the article
Update1
If you want to use nodejs sdk to get the role assignment, please refer to the following code
const authorizationManagement = require('azure-arm-authorization');
const msrestAzure = require('ms-rest-azure');
const scope = '/subscriptions/<subscription>/resourceGroups/<resource-group>/providers/Microsoft.Storage/storageAccounts/<storage-account>/blobServices/default/containers/<container>';
const subscriptionId = 'e5b0fcfa-e859-43f3-8d84-5e5fe29f4c68';
msrestAzure.interactiveLogin().then(credentials => {
const client = new authorizationManagement(credentials, subscriptionId);
client.roleAssignments.listForScope(scope).then(result => {
result.forEach(element => {
client.roleDefinitions.getById(element.roleDefinitionId).then(result => {
console.log("principal ID: "+ element.principalId+"\nrole name: "+result.roleName)
});
});
});
})
For more details, please refer to Get access control list (IAM) of a resource group in Node.js
Update2
According to my test, if you want to use azure-arm-authorization with adal-node. Please refer to the following code
const authorizationManagement = require('azure-arm-authorization');
const TokenCredentials = require('ms-rest').TokenCredentials
const adal = require('adal-node').AuthenticationContext;
const scope = '/subscriptions/<subscription>/resourceGroups/<resource-group>/providers/Microsoft.Storage/storageAccounts/<storage-account>/blobServices/default/containers/<container>';
const subscriptionId = 'e5b0fcfa-e859-43f3-8d84-5e5fe29f4c68';
// use service principal to get access token with adal-node
/*
If you do not have a service principal, you can use the following Azure CLI command(https://learn.microsoft.com/en-us/cli/azure/ad/sp?view=azure-cli-latest#az-ad-sp-create-for-rbac) to create it
az ad sp create-for-rbac -n "MyApp" --role contributor
*/
const tenant = 'your-tenant-id';
const authorityUrl = "https://login.microsoftonline.com/" + tenant;
const clientId = 'your-client-id';
const clientSecret = 'your-client-secret';
const resource = 'https://management.azure.com/';
const context = new adal(authorityUrl);
context.acquireTokenWithClientCredentials(
resource,
clientId,
clientSecret,
(err, tokenResponse) => {
if (err) {
console.log(`Token generation failed due to ${err}`);
} else {
const credentials = new TokenCredentials(tokenResponse.accessToken);
const client = new authorizationManagement(credentials, subscriptionId);
client.roleAssignments.listForScope(scope).then(result => {
result.forEach(element => {
client.roleDefinitions.getById(element.roleDefinitionId).then(result => {
console.log("principal ID: " + element.principalId + "\nrole name: " + result.roleName)
});
});
});
}
}
);
Besides if you want to know how to use passport-azure-ad to get role assignment, you can use passport-azure-ad to get AD access token then call the Azure rest API. Regarding how to implement it, you can refer to the sample.

Access Azure Batch from an Azure Function

I'm trying to use a Service Principle to access a Batch pool from an Azure Function and running into authentication issues that I don't understand. The initial login with the Service Principle works fine, but then using the credentials to access the batch pool returns a 401.
Below is a condensed version of my code with comments at the key points
module.exports.dispatch = function (context) {
MsRest.loginWithServicePrincipalSecret('AppId', 'Secret', 'TennantId', function(err, credentials){
if (err) throw err;
// This works as it prints the credentials
context.log(credentials);
var batch_client = new batch.ServiceClient(credentials, accountUrl);
batch_client.pool.get('mycluster', function(error, result){
if(error === null)
{
context.log('Accessed pool');
context.log(result);
}
else
{
//Request to batch service returns a 401
if(error.statusCode === 404)
{
context.log('Pool not found yet returned 404...');
}
else
{
context.log('Error occurred while retrieving pool data');
context.log(error);
}
//'Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly.
context.res = { body: error.body.message.value };
context.done();
}
});
});
};
How can the initial login with a service principle work no problem, but then the credentials it returns not be able to access the batch pool?
The actual error says to check the auth header on the request, which I can see and the Authorisation header isn't even present.
I've triple checked the Active Directory access control for the batch account the App ID and secret are the ones belonging to the owner of the batch account. Any ideas what to try next?
The credentials expected by the Azure Batch npm client aren't the Azure Active Directory credentials/token, but the keys for the batch account. You can list your keys using the Azure CLI with a command like the following:
az batch account keys list -g "<resource-group-name>" -n "<batch-account-name>"
sample here
Then you can create the credentials parameter with those keys:
var credentials = new batch.SharedKeyCredentials('your-account-name', 'your-account-key');
You could still involve a Service Principal here if you wanted to store your batch keys in something like Key Vault, but then your code would be:
Get Service Principal auth against key vault to fetch name and key
Use name and key to create credentials
You cannot use the same OAuth token returned from the Azure Resource Management endpoint with Batch. Assuming your service principal has the correct RBAC permissions, auth with the Azure Batch endpoint: https://batch.core.windows.net/ instead (assuming you are using Public Azure).
You do not need to get the shared key credentials for the Batch account, credentials via AAD should be used instead if you are using an AAD service principal.
I happened to run across this same issue and I didn't have the option of using SharedKeyCredentials so I wanted to share my solution in case anyone else finds it helpful.
As fpark mentions, we need to get an OAuth token to use with Batch instead of the default Azure Resource Management. Below is the original code posted by Mark with the minor modification needed to make it work with Batch:
module.exports.dispatch = function (context) {
let authOptions = {tokenAudience: 'batch'};
MsRest.loginWithServicePrincipalSecret('AppId', 'Secret', 'TennantId', authOptions, function(err, credentials){
if (err) throw err;
// This works as it prints the credentials
context.log(credentials);
var batch_client = new batch.ServiceClient(credentials, accountUrl);
batch_client.pool.get('mycluster', function(error, result){
if(error === null)
{
context.log('Accessed pool');
context.log(result);
}
else
{
//Request to batch service returns a 401
if(error.statusCode === 404)
{
context.log('Pool not found yet returned 404...');
}
else
{
context.log('Error occurred while retrieving pool data');
context.log(error);
}
//'Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly.
context.res = { body: error.body.message.value };
context.done();
}
});
});
};

Azure: Authorization fail when trying to create notification hub namesapce

Am using the node.js SDK to call the Azure notification hub api, I registered an application using Azure Active directory also i created a resource group and added my user to it as owner. But am getting this error when calling the api:
AuthorizationFailed
The client '333848ca-f996-XXXXXXXXXXXX' with object id
'333848ca-f996-XXXXXXXXX' does not have authorization to perform
action 'Microsoft.NotificationHubs/namespaces/write' over scope
'/subscriptions/ef8e8e-XXXXXXXXXXXX/resourceGroups/notificationGroup/providers/Microsoft.NotificationHubs/namespaces/namespaceapp1
The code:
MsRest.loginWithServicePrincipalSecret(
My Subscription ID,
My Application Secret Key,
My Directory ID, // found under AD -> properties
(err, credentials) => {
if (err) throw err;
// eslint-disable-next-line
const client = new notificationHubsClient(credentials, AZURE_SUBSCRIPTION_ID);
const createNamespaceParameters = {
location: AZURE_LOCATION,
tags: {
tag1: 'value1',
tag2: 'value2',
},
};
client.namespaces.createOrUpdate(AZURE_RESOURCE_GROUP_NAME, req.body.name, createNamespaceParameters,
(error, result) => {
if (error) {
res.send(error);
}
console.info(`Namespace created${JSON.stringify(result, null, ' ')}`);
res.send('app created');
});
},
);
According to the error message, you'll need to add your Azure Active directory application to the Contributor role for creating the namespace in Azure Notification Hub.
You can follow the steps below which are also documented here.
Go to Azure portal and select Subscriptions.
Select the particular subscription (resource group or resource) to assign the application to.
Select Access Control (IAM).
Select Add.
Select the Contributor role to assign to your AAD application.

Resources