Error (Azure Key Vault) is configured for use by Azure Active Directory users only - azure-keyvault

Full Error:
Microsoft.Extensions.Configuration.AzureAppConfiguration.KeyVaultReferenceException: SharedTokenCacheCredential authentication failed: AADSTS9002332: Application 'cfa8b339-82a2-471a-a3c9-0fc0be7a4093'(Azure Key Vault) is configured for use by Azure Active Directory users only. Please do not use the /consumers endpoint to serve this request.
Trace ID: a4b9a7c9-8eb4-48ff-8871-8a63d69b1400
(Azure Key Vault) is configured for use by Azure Active Directory users only. Please do not use the /consumers endpoint to serve this request.
I am walking through the example at this Microsoft Doc page:
https://learn.microsoft.com/en-us/azure/azure-app-configuration/use-key-vault-references-dotnet-core?tabs=powershell%2Ccore3x
No errors on build, but when I launch the site on localhost, I get the above error.
Here is the code in the Program.cs file:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog()
.ConfigureWebHostDefaults(webBuilder =>
webBuilder.ConfigureAppConfiguration((hostingContext, config) =>
{
var settings = config.Build();
config.AddAzureAppConfiguration(options =>
{
options.Connect(settings["ConnectionStrings:AppConfig"])
.ConfigureKeyVault(kv =>
{
kv.SetCredential(new DefaultAzureCredential());
});
});
})
.UseStartup<Startup>());
// See: https://github.com/MicrosoftDocs/azure-docs/issues/71592
Can anyone tell me what to try next?
See: https://github.com/MicrosoftDocs/azure-docs/issues/71592

If you want to access Azure key vault, please refer to the following steps
Create service principal
az ad sp create-for-rbac -n "http://mySP" --sdk-auth
Set access policy in azure keyvault
az keyvault set-policy -n <your-unique-keyvault-name> --spn <clientId-of-your-service-principal> --secret-permissions delete get list set --key-permissions create decrypt delete encrypt get list unwrapKey wrapKey --secret-permissions backup delete get list purge recover restore set
Code
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
webBuilder.ConfigureAppConfiguration((hostingContext, config) =>
{
var settings = config.Build();
config.AddAzureAppConfiguration(options =>
{
options.Connect(settings["ConnectionStrings:AppConfig"])
.ConfigureKeyVault(kv =>
{
var cert = new ClientSecretCredential("<tenant id>", "client id", "client secret");
kv.SetCredential(cert);
});
});
})
.UseStartup<Startup>());
Besides, if you run the application with VS 2019, you can use the extension Azure Service Authentication. But you need to use one work account in the tenant to login and configure right access policy for the account in the key vault. For more details, please refer to here

Related

Azure AD Access token of Client Application does not contain Roles Claims

I have 2 Azure AD applications say Client-App and Server-App in Azure AD App registrations.
Server AD Application:
Registered a new App in Azure AD.
Set up App Roles with name "Search.Address" which is custom role.
Client AD Application:
Registered a new App in Azure AD.
API Permissions: Added the role "Search.Address" created in server-app registration is exposed as an Application Permissions in client app.
Granted Admin access successfully.
I have client Function App created with .NET stack and enabled system managed identity which is associated with Client-App. Client function app runs code to get an access token using ManagedIdentityCredential.
Token is successfully created but role "Search.Address" is missing.
I tried Client-App exposing as an API. But in no vain.
Does Managed identity have any permission to talk to server? How I can assign that using approleassignment ?
public static class Function1
{
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string name = req.Query["name"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
string responseMessage = string.Empty;
try
{
var credential = new ManagedIdentityCredential();
var accessToken = await credential.GetTokenAsync(new TokenRequestContext(scopes: new string[] { "SERVERAPP_ClientID/.default" }) { });
responseMessage = string.IsNullOrEmpty(name)
? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
: $"Hello, {name}. Your Token: {accessToken.Token}";
}
catch (Exception ex)
{
log.LogError(ex.Message+ex.InnerException.Message);
}
return new OkObjectResult(responseMessage);
}
}
Reference:
https://anoopt.medium.com/few-ways-of-obtaining-access-token-in-azure-application-to-application-authentication-40a9473a2dde
You need to assign the application permission/app role to the Managed Identity service principal.
You can't do this through the Portal at the moment, you'll need PowerShell to do that:
Connect-AzureAD
New-AzureADServiceAppRoleAssignment -ObjectId 1606ffaf-7293-4c5b-b971-41ae9122bcfb -Id 32028ccd-3212-4f39-3212-beabd6787d81 -PrincipalId 1606ffaf-7293-4c5b-b971-41ae9122bcfb -ResourceId c3ccaf5a-47d6-4f11-9925-45ec0d833dec
Note that this is for the older AzureAD module. There is also a similar cmdlet for the newer Microsoft.Graph module.
For the AzureAD cmdlet, the ids you need are:
ObjectId and PrincipalId: Managed Identity Service Principal object ID
Id: id of the app role/app permission
ResourceId: object ID of the API Service Principal
Running this command is the same thing as the admin consent for application permissions.
Article I wrote on this: https://joonasw.net/view/calling-your-apis-with-aad-msi-using-app-permissions

.NET IdentityServer4 OpenIdConnect with Discord

I'm trying to cut my teeth with IdentityServer and have been following the guides on readthedocs closely. I'm at the point of adding external identity providers and have added all the ones I want to support to the IdentityServer project.
I specifically want to include "guilds" from Discord then do role based authorization in my web app based on the roles a user has on a specific Guild. Discord lists the various Scopes that are allowed:
So I've included the AspNet.Security.OAuth.Discord package and added an IdentityResource for guilds:
public static class AuthConfig
{
public static IEnumerable<IdentityResource> IdentityResources =>
new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Address(),
new IdentityResources.Email(),
new IdentityResources.Profile(),
new IdentityResource()
{
Name = "guilds",
DisplayName = "Discord Guilds",
Description = "All of the Discord Guilds the user belongs to",
Required = true,
Emphasize = true,
UserClaims = new[] { "name" } // <<< Not sure how I find the claims on the discord api doco
}
};
.
.
.
}
This then allows me to add scopes to my discord options in the startup of my IdentityServer project:
public void ConfigureServices(IServiceCollection services)
{
// uncomment, if you want to add an MVC-based UI
services.AddControllersWithViews();
services.AddAuthentication()
.AddDiscord("Discord", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.ClientId = "<my client id>";
options.ClientSecret = "<my client secret>";
options.Scope.Add("guilds");
})
When I login the uri has the guild scope added and I get the warning on the acknowlegement dialog:
But when I view the content of my claims I don't see anything.
If I add a standard oidc one of email that does display though.
If I follow through to the definition of IdentityResources.Email then I see these claims defined on the ScopeToClaimsMapping property in IdentityServer4.Constants
but I'm not sure how to determine what these claims should be for the Discord guilds scope...and is this even the issue anyway.
Can anyone point me in the right direction?
Claims and Scopes are different but related things.
An scope is a claim, it talks about the scope of your access.
When you request the "guild" scope, it means your token will be able to access the information under that scope. But that doesn't necessarily mean that information is going to be presented in a claim on the token or user_info response.
Instead, what you need to do to get the "guilds" information is to consume their API, using the token.
Discord Developer Portal - Guilds
Get Current User Guilds
GET /users/#me/guilds
Returns a list of partial guild objects the current user is a member of.
Requires the guilds OAuth2 scope.

Azure Key Vault Quickstart fails to provide key vault client

I am learning the Azure Key Vault. I am trying to follow https://learn.microsoft.com/en-us/azure/key-vault/secrets/quick-create-net. I have created a vault in the portal, assigned an access policy for a user account. I have seen the vault in the protal, and I have seen that the user has I have set the environment variable value. I use an 'az login' command at a prompt to use that account. From the prompt I run the code in the quickstart. It prompts for a secret. I enter 'bob.' It throws an exception.
"Creating a secret in [...]-key-vault called 'mySecret' with the value 'bob' ...Unhandled exception. Azure.Identity.AuthenticationFailedException: SharedTokenCacheCredential authentication failed: A configuration issue is preventing authentication - check the error message from the server for details.You can modify the configuration in the application registration portal. See https://aka.ms/msal-net-invalid-client for details. Original exception: AADSTS70002: The client does not exist or is not enabled for consumers. If you are the application developer, configure a new application through the App Registrations in the Azure Portal at https://go.microsoft.com/fwlink/?linkid=2083908."
The exception is at
await client.SetSecretAsync(secretName, secretValue);
I think the problem is coming from
var client = new SecretClient(new Uri(kvUri), new DefaultAzureCredential());
The client is not able to send tokens that the vault accepts. I am at a loss. I have had a couple of people with some expertise in using the Vault review this code and they haven't been able to provide insight. Any help?
Here is the code, from the example:
using System;
using System.Threading.Tasks;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
namespace key_vault_console_app
{
class Program
{
static async Task Main(string[] args)
{
const string secretName = "mySecret";
var keyVaultName = Environment.GetEnvironmentVariable("KEY_VAULT_NAME");
var kvUri = $"https://{keyVaultName}.vault.azure.net";
var client = new SecretClient(new Uri(kvUri), new DefaultAzureCredential());
Console.Write("Input the value of your secret > ");
var secretValue = Console.ReadLine();
Console.Write($"Creating a secret in {keyVaultName} called '{secretName}' with the value '{secretValue}' ...");
await client.SetSecretAsync(secretName, secretValue);
Console.WriteLine(" done.");
Console.WriteLine("Forgetting your secret.");
secretValue = string.Empty;
Console.WriteLine($"Your secret is '{secretValue}'.");
Console.WriteLine($"Retrieving your secret from {keyVaultName}.");
var secret = await client.GetSecretAsync(secretName);
Console.WriteLine($"Your secret is '{secret.Value}'.");
Console.Write($"Deleting your secret from {keyVaultName} ...");
DeleteSecretOperation operation = await client.StartDeleteSecretAsync(secretName);
// You only need to wait for completion if you want to purge or recover the secret.
await operation.WaitForCompletionAsync();
Console.WriteLine(" done.");
Console.Write($"Purging your secret from {keyVaultName} ...");
await client.PurgeDeletedSecretAsync(secretName);
Console.WriteLine(" done.");
}
}
}
Not Sure the root reason for it. But If you want to use a user account to login to Azure and access your key vault, using UsernamePasswordCredential() could be a workaround here.
To use UsernamePasswordCredential(), you should register a client application in Azure AD: Go to Azure portal =>Azure Active Directory => New registration
Note its application ID:
Go to API permissioms, and grant key vault user_impersonation permission so that users could access key vault via this app.
Click "Grant admin consent for.." to finish the permission grant process.
Go to the "Authentication" blade, turn on "Allow public client flows" so that Azure will consider this app as a public client:
Try the code below to create a secret:
using System;
using System.Threading.Tasks;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
namespace key_vault_console_app
{
class Program
{
static async Task Main(string[] args)
{
const string secretName = "mySecret2";
var keyVaultName = "<your kv name>";
var kvUri = $"https://{keyVaultName}.vault.azure.net";
var userCred = new UsernamePasswordCredential("<user account name>", "<user password>", "<your tenant name/id>", "<client application ID WHCIH we created above>");
var client = new SecretClient(new Uri(kvUri), userCred);
Console.Write("Input the value of your secret > ");
var secretValue = Console.ReadLine();
Console.Write($"Creating a secret in {keyVaultName} called '{secretName}' with the value '{secretValue}' ...");
await client.SetSecretAsync(secretName, secretValue);
Console.WriteLine(" done.");
}
}
}
Result:

IdentityServer4 in Docker images running on Linux & Azure repositories & authentication issue

My solution consists of three projects, which are:
An ASP.NET MVC Core project that hosts the IdentityServer.
An ASP.NET Core API project which is protected and manages the IdentityServer.
Another ASP.NET MVC Core that calls the API.
So, the MVC client must sent on each request also an access_token to the API.
If I run the solution with docker-compose command it works, but if I push/pull the images to/from the Azure repositories, I have the issue.
The error that I get is: ErrorMessage: Bearer error="invalid_token", error_description="The signature key was not found"
This is my configuation
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddAspNetIdentity<ApplicationUser>()
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = builder =>
builder.UseNpgsql(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly));
})
.AddOperationalStore(options =>
{
options.ConfigureDbContext = builder =>
builder.UseNpgsql(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly));
options.EnableTokenCleanup = true;
options.TokenCleanupInterval = 30;
});
services.AddAuthentication(IdentityServerConstants.DefaultCookieAuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = EnvironmentReader.AuthorityUrl;
options.ApiName = "api1";
options.RequireHttpsMetadata = false;
});
This is an issue with AddDeveloperSigningCredential vs AddSigningCredential. With AddDeveloperSigningCredential every time you restart IdentityServer, the key material will change all tokens that have been signed with the previous key material will fail to validate. "Temporary" is really only for situations where you don't have other key material available.
the following is from the documentation page found here Documentation
AddDeveloperSigningCredential
Creates temporary key material at startup time. This is for dev only scenarios when you don’t have a certificate to use. The generated key will be persisted to the file system so it stays stable between server restarts (can be disabled by passing false). This addresses issues when the client/api metadata caches get out of sync during development.
VS
AddSigningCredential
Adds a signing key service that provides the specified key material to the various token creation/validation services. You can pass in either an X509Certificate2, a SigningCredential or a reference to a certificate from the certificate store.
My code:
Line from my configuration
services.AddIdentityServer()
.AddSigningCredential(LoadCertificate())
Extra method
private X509Certificate2 LoadCertificate()
{
return new X509Certificate2("../../certs/TestCertificate.pfx",
"pass");
}

To retrieve access token

I have created a MVC application to escalate work to other person inside my organization. I have added all the members in my organization to AAD,
and registered an application there, created app service and linked that app service to registered app with SSO enabled.
Now every time someone visits the app, they can login successfully using their respective credential.
What I want to do know is to retrieve all the members in my AAD and display them inside dropdown list so that anyone can escalate to others by just looking in the dropdown list.
I have tried with sample graph SDK to get the name of users in my organization
with this code
private string redirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
private string appId = ConfigurationManager.AppSettings["ida:AppId"];
private string appSecret = ConfigurationManager.AppSettings["ida:AppSecret"];
private string scopes = ConfigurationManager.AppSettings["ida:GraphScopes"];
public async Task<string> GetUserAccessTokenAsync()
{
string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
HttpContextWrapper httpContext = new HttpContextWrapper(HttpContext.Current);
TokenCache userTokenCache = new SessionTokenCache(signedInUserID, httpContext).GetMsalCacheInstance();
//var cachedItems = tokenCache.ReadItems(appId); // see what's in the cache
ConfidentialClientApplication cca = new ConfidentialClientApplication(
appId,
redirectUri,
new ClientCredential(appSecret),
userTokenCache,
null);
try
{
AuthenticationResult result = await cca.AcquireTokenSilentAsync(scopes.Split(new char[] { ' ' }), cca.Users.First());
return result.AccessToken;
}
// Unable to retrieve the access token silently.
catch (Exception)
{
HttpContext.Current.Request.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties() { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
throw new ServiceException(
new Error
{
Code = GraphErrorCode.AuthenticationFailure.ToString(),
Message = Resource.Error_AuthChallengeNeeded,
});
}
}
with some change in scope.
<add key="ida:AppId" value="xxxxx-xxxxxxx-xxxxxx-xxxxxxx"/>
<add key="ida:AppSecret" value="xxxxxxxxxxx"/>
<add key="ida:RedirectUri" value="http://localhost:55065/"/>
<add key="ida:GraphScopes" value="User.ReadBasic.All User.Read Mail.Send Files.ReadWrite"/>
This enables me to get basic details of all user in my organization.
But how I can achieve this in my app where authentication related stuffs are done in azure only, and there is no code for authentication and authorization in entire solution.
Thanks
Subham, NATHCORP, INDIA
But how I can achieve this in my app where authentication related stuffs are done in azure only, and there is no code for authentication and authorization in entire solution.
Based on my understanding, you are using the build-in feature App Service Authentication / Authorization. You could follow here to configure your web app to use AAD login. And you need to configure the required permissions for your AD app as follows:
Note: For Azure AD graph, you need to set the relevant permissions for the Windows Azure Active Directory API. For Microsoft Graph, you need to configure the Microsoft Graph API.
Then, you need to configure additional settings for your web app. You could access https://resources.azure.com/, choose your web app and update App Service Auth Configuration as follows:
Note: For using Microsoft Graph API, you need to set the resource to https://graph.microsoft.com. Details, you could follow here.
For retrieving the access token in your application, you could get it from the request header X-MS-TOKEN-AAD-ACCESS-TOKEN. Details, you could follow Working with user identities in your application.
Moreover, you could use Microsoft.Azure.ActiveDirectory.GraphClient package for Microsoft Azure Active Directory Graph API, Microsoft.Graph package for Microsoft Graph API using the related access token.

Resources