Reputation: 186
Azure Key Vault supports "Compound Identity" to control access (https://learn.microsoft.com/en-us/azure/key-vault/general/secure-your-key-vault), but is there anyone has some experience on how to perform the authenticatation in .NET using compound identity?
When running from the desktop using native application, I believe the authentication will involve both:
What will the authentication workflow look like? Any example available? Can we achieve from using MSAL?
Suppose we have created an Azure Key Vault and have some secrets saved in that vault. How can I achieve the following functionalities in a desktop application running under Windows 10:
In another word, I want the key vault resource can be accessed by the combination of the two authentication
Upvotes: 2
Views: 4703
Reputation: 186
I'm going to answer my question. The short answer is to use
IConfidentialClientApplication.AcquireTokenOnBehalfOf(
IEnumerable<string> scopes,
UserAssertion userAssertion);
A user token acquired interactively can be used as UserAssertion.
The long version, since I'm a novice user, I'll go through all the details I've found out. It turns out there are bits here and there to create a complete runnable .net application, so not everything is directly related to my question.
Platform: Mobile and desktop application
Setup Certifiates or Secrests: We are going to use secret in this demo.
Redirect URI: add a new one under Mobile and desktop application
section, and set it as http://127.0.0.1
If running as a console application, there is no window directly assoicated to running application, so the easiest way to perform user log-in is to use system default web browser application. Thus the only way for the returned code to consumed is to use redir URL in terms of "http://localhost" or "http://127.0.0.1", aka loopback URL. In run-time, a dynamic port will be used by MSAL as a local webserver to catch the redir URL call from the web browser. Since it is running at local, both http:// or https:// are allowed, unless some one hijacked "localhost" by using DNS or hosts file.
Set-up an API in "Expose an API" section, and added a scope.
In On-Behalf-Of workflow, the user is sign-in using the scope provided by the app instead of directly access the key vault resource. We need to "Set" the Application ID URI, and create at least one scope to be used by interactive sign-in.
Create a key vault.
In "Access Policies", Add new access policy.
To create a Compound Identity, select a valid user or group account for Seelct principal
, and select the same app we created in previous step for Authorized application
.
Create a .NET core console application. Add following nuget packages
<PackageReference Include="Microsoft.Identity.Client" Version="4.18.0" />
<PackageReference Include="Microsoft.IdentityModel.Clients.ActiveDirectory" Version="5.2.8" />
<PackageReference Include="Microsoft.Azure.KeyVault" Version="3.0.5" />
Here is the code using compound identity to acccess the key vault
const string AppClientId = "[Enter_your_Application_(client)_ID";
const string AppClientSecret = "[Enter_your_Application_(secret)";
const string TenantId = "[Enter_your_tenantId]";
const string KeyVaultBaseUri = "https://[your_keyvault_name].vault.azure.net/";
// In on-behalf-of flow, the following scope needs to be consented when acquiring the user token. Otherwise, the app cannot access the key vault on-behalf-of user.
const string KeyVaultUserImScope = "https://vault.azure.net/user_impersonation";
// In on-behalf-of flow, the following scope is used to access key vault data when acquiring client token
const string KeyVaultScope = "https://vault.azure.net/.default";
// An "Exposed API" in app registration is required when using on-behalf-of flow.
const string AppClientScope = "[Enter_your_Application_ID_URI]/[Enter_Your_Scope_Name]";
const string Instance = "https://login.microsoftonline.com/";
Console.WriteLine("Acquire User token");
var pubClient = PublicClientApplicationBuilder.Create(AppClientId)
.WithAuthority($"{Instance}{TenantId}")
.WithRedirectUri("http://localhost") // Make sure the "http://localhost" is added and selected as the app Redirect URI
.Build();
var userResult= pubClient
.AcquireTokenInteractive(new[] {AppClientScope })
.WithExtraScopesToConsent(new [] {KeyVaultUserImScope})
.WithPrompt(Prompt.Consent)
.ExecuteAsync().Result;
// In normal case, when user token is directly given from outside, we should validate if the user Result has consented to the required customized scope AppClientScope before proceeded with next steps. Here we will ignore this step.
Console.WriteLine("Acquire Client token");
// The following two steps are equivalent to https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow#middle-tier-access-token-request
var conClient = ConfidentialClientApplicationBuilder.Create(AppClientId)
.WithAuthority($"{Instance}{TenantId}")
.WithClientSecret(AppClientSecret)
.Build();
var OboResult= conClient.AcquireTokenOnBehalfOf(
new[] {KeyVaultScope},
new UserAssertion(userReult.AccessToken))
.ExecuteAsync().Result;
Console.WriteLine("Access Key Vault");
var kc = new KeyVaultCredential((authority, resource, scope) =>
{
Console.WriteLine($"Authority: {authority}, Resource: {resource}, Scope: {scope}");
return Task.FromResult(OboResult.AccessToken);
});
var kvClient = new KeyVaultClient(kc);
var secretBundle = await kvClient.GetSecretAsync(KeyVaultBaseUri, SecretName);
Console.WriteLine("Secret:" + secretBundle.Value);
If we are not using compound identity, we can use Azure.Security.KeyVault.Secrets.SecretClient
to access the key vault data by one of the following method
// For access policy assigned to confidential application
var client = new SecretClient(new Uri(KeyVaultBaseUri),
new ClientSecretCredential(TenantId, AppClientId, AppClientSecret));
var secretBundle = await client.GetSecretAsync(SecretName);
Console.WriteLine("Secret:" + secretBundle.Value.Value);
and
// For access policy assigned to User or Group account
var client = new SecretClient(new Uri(KeyVaultBaseUri), new InteractiveBrowserCredential());
var secretBundle = await client.GetSecretAsync(SecretName);
Console.WriteLine("Secret:" + secretBundle.Value.Value);
Upvotes: 5
Reputation: 58743
Actually if you give access permissions to e.g. secrets, it doesn't allow users to view the Key Vault in Azure Portal. If they don't have read access to the resource in Azure RBAC, they can't view it. So you should be able to add access to the users and call it directly from the app on behalf of the user.
Another approach would be to use a back-end that checks the user id and accesses the Key Vault instead of the user.
Here you only need to allow the back-end app access to Key Vault itself.
Upvotes: 1