I am new to Azure Cloud and trying to follow one tutorial on how to connect with Azure Key Vault. Here is the link for the tutorial.
Program.cs
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureAppConfiguration((context, config) =>
{
var builtConfig = config.Build();
var vaultName = builtConfig["VaultName"];
var keyVaultClient = new KeyVaultClient(async (authority, resource, scope) =>
{
var credential = new DefaultAzureCredential(false);
var token = credential.GetToken(
new Azure.Core.TokenRequestContext(
new[] { "https://vault.azure.net/.default" }));
return token.Token;
});
config.AddAzureKeyVault(vaultName, keyVaultClient, new DefaultKeyVaultSecretManager());
});
appsettings.json
"VaultName": "https://connectionstringkeyvault.vault.azure.net/"
In Azure, I have created a Key Vault with the name ConnectionStringKeyVault and I have defined a secret as well.
This is the access policy that I have created in Key Vault:
And, I have created a storage account with the following details:
But, whenever I try to execute my code I am getting the below exception:
What am I doing wrong?
Solution 1:
To fix access denied you need to configure Active Directory permissions. Grant access to KeyVault.
1. Using PowerShell Run the command:
Set-AzureRmKeyVaultAccessPolicy -VaultName 'XXXXXXX' -ServicePrincipalName XXXXX -PermissionsToKeys decrypt,sign,get,unwrapKey
2. Using the Azure portal
Open Key Vaults
Select Access Policies from the Key Vault resource blade.
Click the [+ Add Access Policy] button at the top of the blade
Click Select Principal to select the application you created earlier
From the Key permissions drop down, select "Decrypt", "Sign", "Get",
"UnwrapKey" permissions
Save changes
Solution 2:
According to https://learn.microsoft.com/en-us/dotnet/api/overview/azure/identity-readme, Try with replacing the DefaultAzureCredential with ChainedTokenCredential, the key value secret was successfully retrieved.
Example code:
var cred = new ChainedTokenCredential(new ManagedIdentityCredential(), new AzureCliCredential());
SecretClient client = new SecretClient(new Uri(keyvaultUri), cred);
Response<KeyVaultSecret> secret = await client.GetSecretAsync("kv-sec-test");
For more details refer this document
Related
I have enabled authentication for azure ad by following some sample apps from MS as shown below. I then use the authorize statements below as well so my application should be locked down unless the user is authenticated. My issue in my testing environment is if i restart IIS express then when it starts back up I am not required to log back in if the browser is still open. I can openly navigate my application but when any function related to MS Graph is called it fails with error: ErrorCode: user_null Microsoft.Identity.Client.MsalUiRequiredException: No account or login hint was passed to the AcquireTokenSilent call. So its as if my application thinks I am still authorized but really I am not because the MS Graph api call fails because of token related issues. If I force myself to logout then log back in everything works. For my live site if a user is inactive for some time but the browser is left open this same issue occurs and they are not forced to relogin so these issues again can happen until they force logout and re log back in. What have I setup wrong or need to add to force relogin before these issues arise? Or can I keep the token working without forcing the user to relogin?
// Add services to the container.
builder.Services.AddRazorPages().AddRazorPagesOptions(options =>
{
options.Conventions.AllowAnonymousToFolder("/Login");
options.Conventions.AuthorizeFolder("/");
options.Conventions.AuthorizeFolder("/files");
});
//authentication pipline
builder.Services.AddHttpContextAccessor();
var initialScopes = builder.Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' ');
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(options =>
{
builder.Configuration.Bind("AzureAd", options);
options.Events = new OpenIdConnectEvents
{
//Tap into this event to add a UserID Claim to a new HttpContext identity
OnTokenValidated = context =>
{
//This query returns the UserID from the DB by sending the email address in the claim from Azure AD
string query = "select dbo.A2F_0013_ReturnUserIDForEmail(#Email) as UserID";
string connectionString = builder.Configuration.GetValue<string>("ConnectionStrings:DBContext");
string signInEmailAddress = context.Principal.FindFirstValue("preferred_username");
using (var connection = new SqlConnection(connectionString))
{
var queryResult = connection.QueryFirst(query, new { Email = signInEmailAddress });
var claims = new List<Claim>
{
new Claim("UserID", queryResult.UserID.ToString())
};
var appIdentity = new ClaimsIdentity(claims);
context.Principal.AddIdentity(appIdentity);
}
return Task.CompletedTask;
},
};
})
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi"))
.AddInMemoryTokenCaches();
//Add Transient Services
builder.Services.AddTransient<IOneDrive, OneDrive>();
builder.Services.AddControllers(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
builder.Services.AddRazorPages()
.AddMicrosoftIdentityUI();
Then in all of my controllers I use:
[AuthorizeForScopes(ScopeKeySection = "DownstreamApi:Scopes")]
Adding
var accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(initialScopes);
before my graph api call fixes my issue. However, I feel as though I shouldnt need to call this everytime but only if the token expires. So is there a way to check if the token expires?
There are two things that worked for me. One was trying to get a token manually if an error occurred using the following:
var accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(initialScopes);
The other solution that seemed to work the same was to add the following catch as well:
try
{
Console.WriteLine($"{svcex}");
string claimChallenge = WwwAuthenticateParameters.GetClaimChallengeFromResponseHeaders(svcex.ResponseHeaders);
_consentHandler.ChallengeUser(initialScopes, claimChallenge);
}
catch (Exception ex2)
{
_consentHandler.HandleException(ex2);
}
I do not know enough about what consent handler does to provide more info on why this works but will update my answer once I do.
I have created the application Insights using ARM template with C# code.
var creds = new AzureCredentialsFactory().FromServicePrincipal(client, key, tenant, AzureEnvironment.AzureGlobalCloud);
IAzure azure = Microsoft.Azure.Management.Fluent.Azure.Authenticate(creds).WithSubscription(subscription);
IDeployment deployement = azure.Deployments.Define("my-app")
.WithExistingResourceGroup("my-rg-grp")
.WithTemplate(template)
.WithParameters("{}")
.WithMode(DeploymentMode.Incremental)
.CreateAsync();
deployment doesn't have the InstrumentationKey in response.
How could I get the InstrumentationKey just after the Application Insights creation using ARM?
You can use ApplicationInsightsManagementClient class to get the ApplicationInsights resources and the relevant property. The class is defined at Microsoft.Azure.Management.ApplicationInsights v0.3.0-preview package
ApplicationInsightsManagementClient applicationInsightsManagementClient =
new ApplicationInsightsManagementClient(creds) { SubscriptionId = subscriptionId };
var appliationInsightComponents = await applicationInsightsManagementClient.Components.ListAsync();
var requiredApplicationInsightComponent = appliationInsightComponents.SingleOrDefault(a =>
a.ApplicationId.Equals("<<Name of resource>>", StringComparison.OrdinalIgnoreCase));
// to get the InstrumentationKey use
requiredApplicationInsightComponent.InstrumentationKey
I use the following code to access my Azure KeyVault
public static string GetKeyVaultSecret(string keyVaultName, string secretName)
{
string secret = "";
string secretUrl = $"https://{keyVaultName}.vault.azure.net/secrets/{secretName}";
AzureServiceTokenProvider azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
Task.Run(async () => {
var secretObject = await keyVaultClient.GetSecretAsync(secretUrl).ConfigureAwait(false);
secret = secretObject.Value;
}).GetAwaiter().GetResult();
return secret;
}
This works perfectly when I am logged in under my account. But of a login as a service account I get the error:
Parameters: Connection String: [No connection string specified], Resource:
https://vault.azure.net, Authority: https://login.windows.net/5a47d63b-1b7e-4d2d-9333-750184dcbc99.
Exception Message: Tried to get token using Active Directory Integrated Authentication.
Access token could not be acquired. unknown_user_type: Unknown User Type
I would like only the certificate to be used to authenticate and authorize access to the KeyVault and not in addition any Azure Active Directory account
You'll need to set a connection string environment variable that points to the certificate and can be read by the application.
This is taken from https://learn.microsoft.com/en-us/dotnet/api/overview/azure/service-to-service-authentication#use-a-certificate-in-local-keystore-to-sign-into-azure-ad
I am trying to get azure users and i am getting permissions error, even if instead of Users i place ME, why ? shouldnt it be something that i had no need to have admin consent? Any help is appreciated!!
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create("****")
.WithTenantId("****")
.WithClientSecret("***")
.Build();
ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
// Create a new instance of GraphServiceClient with the authentication provider.
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
var user = await graphClient.Users
.Request()
.WithScopes(graphScopes)
.Select(u => new {
u.DisplayName
})
.GetAsync();
```
For this problem it seems you do not have permission to get the users. You can refer to the document of the graph api, you need the permission shown as below:
So please go to the application which registered in your azure ad, and click "API permissions", then add the permission into it.
After add the permissions, please do not forget click "Grand admin consent for xxx".
For the question why it still failed when you change the Users to me. You use client credential flow to do authentication to request the graph api, you just provide the information of clientId, tenantId and clientSecret. So it doesn't contain a user(or yourself) information. So you can't use .Me. If you want to use .Me, you can use password grant flow. It contains the user information, so system know who is .Me.
================================Update==============================
If you want to use delegated permission(such as User.ReadBasic.All), you can't use client_credential. Now your code use client_credential flow, the access token doesn't contain user identity. I provide a sample of username/password flow(and use delegated permission User.ReadBasic.All) below for your referencef:
In the "API permissions" tab of the registered app, I just add one permission User.ReadBasic.All.
The code shown as below:
using Microsoft.Graph;
using Microsoft.Graph.Auth;
using Microsoft.Identity.Client;
using System;
using System.Security;
namespace ConsoleApp3
{
class Program
{
static async System.Threading.Tasks.Task Main(string[] args)
{
IPublicClientApplication publicClientApplication = PublicClientApplicationBuilder
.Create("<clientId>")
.WithTenantId("<tenantId>")
.Build();
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
UsernamePasswordProvider authProvider = new UsernamePasswordProvider(publicClientApplication, scopes);
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
var str = "<your password>";
var password = new SecureString();
foreach (char c in str) password.AppendChar(c);
var users = await graphClient.Users.Request().WithUsernamePassword("<your account/email>", password).GetAsync();
Console.WriteLine(users.Count);
}
}
}
And before run the code, you need to do "consent to use the application" once. You need to browse the url as this: https://login.microsoftonline.com/<tenantId>/oauth2/v2.0/authorize?client_id=<clientId>&response_type=code&redirect_uri=<redirectUri>&response_mode=query&scope=openid https://graph.microsoft.com/.default&state=12345 in your browser and the page will show as:
Click "Accept", then you can run the code to get user list success.
I am writing a program that tries to access a secret (OneAuthZAuthentication) to an Azure Table Storage through accessing KeyVault. I am following the steps listed in this tutorial: https://jeanpaul.cloud/2019/12/07/azure-key-vault-access-from-c/
I have created a Key Vault called ITALocalBuildSecrets:
With the following DNS Name: https://italocalbuildsecrets.vault.azure.net/
I also have another secret with the following name (OneAuthZAuthentication):
I have created an app in the active directory (OneAuthZUserApplication), and you can see the Application (client) ID displayed below:
I created a client secret for OneAuthZUserApplication:
I authorized a Console Application (OneAuthZUserApplication) as an access policy:
And you can clearly see the access policy being registered:
Below is the code I am running:
// Retrieves the access token necessary to gain authentication into the key vault
[FunctionName("GetToken")]
public static async System.Threading.Tasks.Task<string> GetToken(string authority, string resource, string scope)
{
var clientId = "5cf497b0-3467-456a-a03a-4d4414b*****"; // Stars are for security reasons :D
var clientSecret = "468.26i5Wc.nQ6TYL-eOvBmcto.t.*****"; // Stars are for security reasons
ClientCredential credential = new ClientCredential(clientId, clientSecret);
var context = new AuthenticationContext(authority, TokenCache.DefaultShared);
var result = await context.AcquireTokenAsync(resource, credential);
return result.AccessToken;
}
// Retrieves the access key vault accountKey (needed to authenticate access into the role assignments table)
public static string GetVaultValue()
{
KeyVaultClient client = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetToken));
var vaultAddress = "https://italocalbuildsecrets.vault.azure.net/";
var secretName = "OneAuthZAuthentication";
var secret = client.GetSecretAsync(vaultAddress, secretName).GetAwaiter().GetResult();
return secret.Value;
}
[FunctionName("Function1")]
// Function that reads a small portion of the role assignments table (OneAuthZRoleAssignments) every
// configurable number of times
public static async System.Threading.Tasks.Task RunAsync([TimerTrigger("%TimerTriggerPeriod%")]TimerInfo myTimer, ILogger log)
{
Console.WriteLine($"Secret Value from Vault is: {GetVaultValue()}");
}
I get the following error:
Function1. Microsoft.Azure.KeyVault: Operation returned an invalid status code 'Forbidden'.
This does seems strange, considering that I authorized the OneAuthZUserApplication application to the key vault.
I follow you steps and use your code to test, and it all works very well.
Please go to confirm after adding Access policy, remember to click save button.
What is the authority you are using? Further, I think you are missing the step of configuring scopes when getting the token. Similar here, but using MSAL.
string[] scopeArray = new string[] { "https://vault.azure.net/.default" };
And provide that to your token request.
Also, if these are Azure Functions, why don't you use the function MSI to retrieve the secret? See here