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:
Related
Edit:
The problem was a simple typo in the Header. You're probably wasting your time here
In essence, I have the same problem as described here. It's a somewhat different usecase and I'll try to provide as much context as I can in the hopes that someone will be able to solve the problem.
So, this has to do with Azure, which seems to be an Alias for "Crazy problem generator". My apologies.
I'm trying to write a Service in NodeJS which has the purpose of synchronizing another app's database with data from Azure.
For that reason, I'm using msal-node's Client Credential Flow as described here.
I find their comment // replace with your resource quite ridiculous, as I have not found a single full example online that specifies the format that should be used.
Intuitively, I would use something like
['GroupMember.Read.All']
//or
['https://graph.microsoft.com/GroupMember.Read.All']
Unfortunately, this does not work. Luckily, I get an error that describes the problem (even if only when this is the only scope I use, othewise the error is garbage):
{
// ...
errorMessage: '1002012 - [2022-05-23 11:39:00Z]: AADSTS1002012: The provided value for scope https://graph.microsoft.com/bla openid profile offline_access is not valid. Client credential flows must have a scope value with /.default suffixed to the resource identifier (application ID URI).\r\n'
}
Okay, let's do that:
['https://graph.microsoft.com/GroupMember.Read.All/.default']
Now, the app actually performs a request, but unfortunately, I get
{
// ...
errorCode: 'invalid_resource',
errorMessage: '500011 - [2022-05-23 11:42:31Z]: AADSTS500011: The resource principal named https://graph.microsoft.com/GroupMember.Read.All was not found in the tenant named <My company name, not an ID as shown in some places>. This can happen if the application has not
been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant.\r\n' +
'Trace ID: <some id>\r\n' +
'Correlation ID: <some id>\r\n' +
'Timestamp: 2022-05-23 11:42:31Z - Correlation ID: <some id> - Trace ID: <some id>',
}
And yet it is there
And I am able to get a token for the .default scope. That's just not good for anything.
The important parts of the actual code:
import fetch from 'isomorphic-fetch';
import * as msal from '#azure/msal-node';
// got env variables using dotenv package
// this is Typescript
const msalConfig = {
auth: {
clientId: process.env.OAUTH_APP_ID!,
authority: process.env.OAUTH_AUTHORITY!,
clientSecret: process.env.OAUTH_APP_SECRET!
},
system: {
loggerOptions: {
loggerCallback(loglevel: any, message: any, containsPii: any) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose,
}
}
};
const msalClient = new msal.ConfidentialClientApplication(msalConfig);
const allCompanyMembersGroupId = '<some id>';
const tokenRequest = {
scopes: ['https://graph.microsoft.com/GroupMember.Read.All/.default']
};
msalClient.acquireTokenByClientCredential(tokenRequest).then(response => {
console.log('Got token:', response);
fetch(`https://graph.microsoft.com/v1.0/groups/${allCompanyMembersGroupId}/members`, {
method: 'GET',
headers: {
Authority: `Bearer ${response!.accessToken}`
}
}).then((res: any) => {
console.log('Got response:', res);
})
});
As mentioned, the request isn't performed with my GroupMember.Read.All scope. With the default scope, I get a 401 unauthorized error.
So, these are my questions:
How to fix this?
Okay, if it you don't know how to fix it, what is the exact format required for the scope? Is the prefix https://graph.microsoft.com correct, even for my specific app?
Is this the correct library to use, or is this just broken code or not intended for such use? The other question I linked to above mentions that requests were successful using Postman, just not this lib...
Thanks heaps for any advice!
It's a problem with the headers. It should be Authorization instead of Authority.
The error is in the following variable:
const tokenRequest = {
scopes: ['https://graph.microsoft.com/GroupMember.Read.All/.default']
};
the correct one would be:
const tokenRequest = {
scopes: ['https://graph.microsoft.com/.default']
};
Note: When using Client Credentials flow, it is a must to specify the scope string tailing with "/.default".
For eg:
If you are trying to get an access-token using client credentials
flow for MS Graph API, the scope should be
"https://graph.microsoft.com/.default".
If you are trying to get an
access-token using client credentials flow for a custom API which is
registered in AAD, the scope should be either
"api://{API-Domain-Name}/.default" or
"api://{API-Client-Id}/.default".
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.
I have this code in nextjs that is supposed to check if a token is valid then sign in the user.
const firebaseAdmin = require("firebase-admin");
const serviceAccount = require ('../secret.json');
export const verifyIdToken = async (token) => {
if (!firebaseAdmin.apps.length) {
firebaseAdmin.initializeApp({
// credential: firebaseAdmin.credential.cert(serviceAccount),
credential: firebaseAdmin.credential.applicationDefault(),
databaseURL: "rtdb.firebaseio.com",
});
}
return await firebaseAdmin
.auth()
.verifyIdToken(token)
.catch((error) => {
throw error;
});
};
I have the windows environment variables set as firebase recommends and switched to using the applicationDefault() since as I understand,
ADC can automatically find your credentials
Problem is the application works only locally. When I deploy the website, the token is not verified and creates errors. I am serving the NextJs app through a cloud function. How can I solve this.
The error is
auth/invalid-credential
Must initialize app with a cert credential or set your Firebase project
ID as the GOOGLE_CLOUD_PROJECT environment variable to call verifyIdToken().
What the app is supposed to do is do a check server side to determine if a token is valid.
As below
export async function getServerSideProps(ctx) {
try {
const cookies = nookies.get(ctx);
const token = await verifyIdToken(cookies.token);
// the user is authenticated!
const { uid, email } = token;
return {
props: {
userData: {
email: email,
uid: uid,
},
},
};
} catch (err) {
console.log(err.code)
console.log(err.message)
return { props: {
} };
}
}
The auth/invalid-credential error message means that the Admin SDK needs to be initialized, as we can see in the Official Documentation.
The credential used to authenticate the Admin SDKs cannot be used to
perform the desired action. Certain Authentication methods such as
createCustomToken() and verifyIdToken() require the SDK to be
initialized with a certificate credential as opposed to a refresh
token or Application Default credential.
And for the ID token verification, a project ID is required. The Firebase Admin SDK attempts to obtain a project ID via one of the following methods:
If the SDK was initialized with an explicit projectId app option, the SDK uses the value of that option.
If the SDK was initialized with service account credentials, the SDK uses the project_id field of the service account JSON object.
If the GOOGLE_CLOUD_PROJECT environment variable is set, the SDK uses its value as the project ID. This environment variable is available for code running on Google infrastructure such as App Engine and Compute Engine.
So, we can initialize the Admin SDK with a service (and fulfill the second option); but, the first thing to do is authenticate a service account and authorize it to access Firebase services, you must generate a private key file in JSON format.
To generate a private key file for your service account you can do the following:
In the Firebase console, open Settings > Service Accounts.
Click Generate New Private Key, then confirm by clicking Generate Key.
Securely store the JSON file containing the key.
Once you have your JSON file, you can set a environment variable to hold your private key.
export GOOGLE_APPLICATION_CREDENTIALS="/home/user/Downloads/service-account-file.json"
And then, use it in your code like this:
admin.initializeApp({
credential: admin.credential.applicationDefault(),
databaseURL: 'https://<DATABASE_NAME>.firebaseio.com'
});
In the end I downloaded Gcloud tool and setting the GOOGLE_APPLICATION_CREDENTIALS environment variable from the tool worked. The function could then work with credential: firebaseAdmin.credential.applicationDefault(),
I followed instructions on the page to create an angular app to use with MSAL 2.0 and AD B2C. I'm able to login and see the data in the Local Storage. Please see the picture below:
My questions are:
Should I see the following data for credentialType and clientId and authorityType in the Local Storage?
What should I do now to get the address and few other details of this logged in user?
Yes, it’s expected to see this on the client. You can configure it between local or session storage.
https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-js-sso#sso-between-browser-tabs
const config = {
auth: {
clientId: "abcd-ef12-gh34-ikkl-ashdjhlhsdg"
},
cache: {
cacheLocation: 'localStorage' //sessionstorage
}
}
Use getAccount() to fetch the claim set from the token.
function signIn() {
myMSALObj.loginPopup(loginRequest)
.then(loginResponse => {
console.log("id_token acquired at: " + new Date().toString());
if (myMSALObj.getAccount()) {
updateUI();
}
The following is in a console application and ClientID, RedirectUri is from the created native app in azure active directory.
var authContext = new AuthenticationContext(string.Format("https://login.windows.net/{0}","common"),new FileCache());
var token = authContext.AcquireToken("https://management.core.windows.net/", ClientID, RedirectUri, PromptBehavior.Auto);
I now have the token for talking with management api.
using (var client = new KeyVaultManagementClient(new TokenCloudCredentials(SubscriptionId, token.AccessToken)))
{
var a = client.Vaults.List(resourceGroup, 10);
foreach(var vault in a.Vaults)
{
var vaultInfo = client.Vaults.Get(resourceGroup, vault.Name);
Console.WriteLine(JsonConvert.SerializeObject(vaultInfo.Vault, Formatting.Indented));
//Verifying that the AccessPolicies contains my object id (pasting idtoken into jwt.io and compare with oid claim) Success.
// Now its time to talk with keyvault
var keyvault = new KeyVaultClient(GetAccessTokenAsync);
var secrets = keyvault.GetSecretsAsync(vaultInfo.Vault.Properties.VaultUri).GetAwaiter().GetResult();
}
}
private static Task<string> GetAccessTokenAsync(string authority, string resource, string scope)
{
var context = new AuthenticationContext(authority, new FileCache());
var result = context.AcquireToken(resource, new ClientCredential(AppClientId,AppKey));
return Task.FromResult(result.AccessToken);
}
Above works but require me to create a separate app on my AD that can talk with the keyvault. I would like to use my own ID to talk with keyvault, but I cant figure out how to get the access token that the keyvault client require.
Do i need to update the manifest on azure manuel and adding that my console app is allowed to get a token on behalf of users to keyvault?
What code is needed to be changed in GetAccessTokenAsync to make it work.
I have tried giving it just the access or id tokens from the initial token request from the common endpoint. Do anyone have some suggestions on how to talk to azure key vault on behalf of my own id and not an app?
Update
So looking at headers i found out my token was missing vault.azure.net as resource and therefore trying:
var testtoken = authContext.AcquireToken("https://vault.azure.net", ClientID, RedirectUri);
gives the following error:
AADSTS65005: The client application has requested access to resource
'https://vault.azure.net'. This request has failed because the client
has not specified this resource in its requiredResourceAccess list.
and looking at the current manifest:
"requiredResourceAccess": [
{
"resourceAppId": "797f4846-ba00-4fd7-ba43-dac1f8f63013",
"resourceAccess": [
{
"id": "41094075-9dad-400e-a0bd-54e686782033",
"type": "Scope"
}
]
},
{
"resourceAppId": "00000002-0000-0000-c000-000000000000",
"resourceAccess": [
{
"id": "311a71cc-e848-46a1-bdf8-97ff7156d8e6",
"type": "Scope"
}
]
}
],
How do i know what guids to use for scope and resourceAppId for the keyvault?
Temp Solution
Until i know how to get the resourceAppId and related information I am using the old trick of impersonating the powershell tools.
var vaultToken = authContext.AcquireToken("https://vault.azure.net", "1950a258-227b-4e31-a9cf-717495945fc2", new Uri("urn:ietf:wg:oauth:2.0:oob"));
var keyvault = new KeyVaultClient((_, b, c) => Task.FromResult(vaultToken.AccessToken));
var secrets = keyvault.GetSecretsAsync(vaultInfo.Vault.Properties.VaultUri).GetAwaiter().GetResult();
source: http://www.s-innovations.net/Blog/2014/02/12/Controlling-the-login-flow-when-using-ADAL-for-WAML
Please also read #bradygaster comment at the blog post before using the powershells clientid.
You're on the right track! You need to configure AAD to be able to authorize users specifically for access to KeyVault. Try adding the following to your manifest.
{
"resourceAppId": "cfa8b339-82a2-471a-a3c9-0fc0be7a4093",
"resourceAccess": [
{
"id": "f53da476-18e3-4152-8e01-aec403e6edc0",
"type": "Scope"
}
]
}
If that doesn't work, you can do this the old-fashioned way by visiting the old portal, navigating to AAD, your AAD Tenant, your application, and adding "Azure Key Vault" under the "permissions to other applications" section of the "Configure" tab.
Here is what you need to do:
Create a service principal
Register it in Azure AD
Grant it access to the Azure KeyVault API
The steps were documented in an Azure article last September at
https://blogs.technet.microsoft.com/kv/2016/09/17/accessing-key-vault-from-a-native-application/
This article explains how to perform the above steps to access Azure KeyVault programmatically from a native application (as opposed to a service application) without having to rely on the Azure Powershell trick mentioned by #benv.