1
votes

Question:

Azure Key Vault supports "Compound Identity" to control access (https://docs.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:

  1. user's interactive sign-in and consent, and
  2. the application's authentication with the client secret

What will the authentication workflow look like? Any example available? Can we achieve from using MSAL?

Som explanation about the compound identity:

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:

  • The current Windows user is part of the Azure AD group GroupA under the same tenant of the Azure Key Vault.
  • When the current user launches the desktop application from the same user session, the application can access the secrets within the said key vault.
  • The current user cannot access the secrets directly when they log in to the azure portal from their browsers.
  • If the application is launched by another user who is not part of the Azure AD group (GroupA), the application cannot access the secrets within the said key vault.

In another word, I want the key vault resource can be accessed by the combination of the two authentication

  • The user credential, and
  • the application secret
2

2 Answers

1
votes

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.

1. Create an application registration in Azure AD.

  1. Platform: Mobile and desktop application

  2. Setup Certifiates or Secrests: We are going to use secret in this demo.

  3. 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.

  4. 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.

2. Create the key vault, and set up access policies in data plane.

  1. Create a key vault.

  2. 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.

3. Write code

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://docs.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);
0
votes

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.

  • User logs in to the app
  • App acquires access token to the back-end for the user
  • App calls back-end with token
  • Back-end verifies token, checks if this user is allowed access
  • Get the secret from Key Vault with an access token acquired with only back-end client credentials

Here you only need to allow the back-end app access to Key Vault itself.