1

Trying to integrate Azure KeyVault into an On-Premises ASP.NET 4.7.1 WebForms Application and authenticating as a SPN using CertificateSubjectName but it is failing. I have even reproduced this in a sample application. I have an ASP.NET 4.7.1 + AngularJS application and it is working fine there. Not sure what is special about WebForms. Can someone help please? Here are the error screenshots: WebError1

Here are more of the StackTrace details:

Stack Trace: 



[AzureServiceTokenProviderException: Parameters: Connectionstring: RunAs=App;AppId=************;TenantId=************;CertificateSubjectName=CN=#####, OU=###, O=#####, L=#####, S=Virginia, C=US;CertificateStoreLocation=LocalMachine, Resource: https://vault.azure.net, Authority: https://login.windows.net/*****************. Exception Message: Tried 1 certificate(s). Access token could not be acquired.
Exception for cert #1 with thumbprint XXXXXXXXXXXXXXXXXXX: Object reference not set to an instance of an object.]
   Microsoft.Azure.Services.AppAuthentication.<GetAccessTokenAsyncImpl>d__14.MoveNext() +1943
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +102
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +64
   Microsoft.Azure.KeyVault.<PostAuthenticate>d__9.MoveNext() +422
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +102
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +64
   Microsoft.Azure.KeyVault.<ProcessHttpRequestAsync>d__10.MoveNext() +1113
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +102
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +64
   System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task) +26
   Microsoft.Azure.KeyVault.<GetSecretsWithHttpMessagesAsync>d__66.MoveNext() +2018
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +102
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +64
   Microsoft.Azure.KeyVault.<GetSecretsAsync>d__49.MoveNext() +272
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +102
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +64
   Microsoft.Configuration.ConfigurationBuilders.<<GetAllKeys>b__17_0>d.MoveNext() +161

[AggregateException: One or more errors occurred.]
   System.AggregateException.Handle(Func`2 predicate) +5434660
   Microsoft.Configuration.ConfigurationBuilders.AzureKeyVaultConfigBuilder.GetAllKeys() +582
   Microsoft.Configuration.ConfigurationBuilders.AzureKeyVaultConfigBuilder.Initialize(String name, NameValueCollection config) +563
   System.Configuration.ConfigurationBuildersSection.CreateAndInitializeBuilderWithAssert(Type t, ProviderSettings ps) +309

Here is web.config

<?xml version="1.0" encoding="utf-8"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  https://go.microsoft.com/fwlink/?LinkId=169433
  -->
<configuration>
  <configSections>
    <section name="configBuilders" type="System.Configuration.ConfigurationBuildersSection, System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" restartOnExternalChanges="false" requirePermission="false" />
  </configSections>
  <configBuilders>
    <builders>
      <add name="AzureKeyVault" vaultName="****-kv" connectionString="RunAs=App;AppId=*********;TenantId=*******;CertificateSubjectName=CN=**********-KV, OU=****, O=*********, L=*******, S=Virginia, C=US;CertificateStoreLocation=LocalMachine" type="Microsoft.Configuration.ConfigurationBuilders.AzureKeyVaultConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Azure, Version=1.0.0.0, Culture=neutral" />
    </builders>
  </configBuilders>
  <appSettings configBuilders="AzureKeyVault">
    <add key="foo-setting" value="" />
  </appSettings>
  <system.web>
    <compilation debug="true" targetFramework="4.7.1" />
    <httpRuntime targetFramework="4.7.1" />
    <pages>
      <namespaces>
        <add namespace="System.Web.Optimization" />
      </namespaces>
      <controls>
        <add assembly="Microsoft.AspNet.Web.Optimization.WebForms" namespace="Microsoft.AspNet.Web.Optimization.WebForms" tagPrefix="webopt" />
      </controls>
    </pages>
  </system.web>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Antlr3.Runtime" publicKeyToken="eb42632606e9261f" />
        <bindingRedirect oldVersion="0.0.0.0-3.5.0.2" newVersion="3.5.0.2" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" />
        <bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-1.6.5135.21930" newVersion="1.6.5135.21930" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
  <system.codedom>
    <compilers>
      <compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=2.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:default /nowarn:1659;1699;1701" />
      <compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=2.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:default /nowarn:41008 /define:_MYTYPE=\&quot;Web\&quot; /optionInfer+" />
    </compilers>
  </system.codedom>
</configuration>

Here is packages.config

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Antlr" version="3.5.0.2" targetFramework="net471" />
  <package id="AspNet.ScriptManager.bootstrap" version="3.4.1" targetFramework="net471" />
  <package id="AspNet.ScriptManager.jQuery" version="3.4.1" targetFramework="net471" />
  <package id="bootstrap" version="3.4.1" targetFramework="net471" />
  <package id="jQuery" version="3.4.1" targetFramework="net471" />
  <package id="Microsoft.AspNet.FriendlyUrls" version="1.0.2" targetFramework="net471" />
  <package id="Microsoft.AspNet.FriendlyUrls.Core" version="1.0.2" targetFramework="net471" />
  <package id="Microsoft.AspNet.ScriptManager.MSAjax" version="5.0.0" targetFramework="net471" />
  <package id="Microsoft.AspNet.ScriptManager.WebForms" version="5.0.0" targetFramework="net471" />
  <package id="Microsoft.AspNet.Web.Optimization" version="1.1.3" targetFramework="net471" />
  <package id="Microsoft.AspNet.Web.Optimization.WebForms" version="1.1.3" targetFramework="net471" />
  <package id="Microsoft.Azure.KeyVault" version="2.3.2" targetFramework="net471" />
  <package id="Microsoft.Azure.KeyVault.WebKey" version="2.0.7" targetFramework="net471" />
  <package id="Microsoft.Azure.Services.AppAuthentication" version="1.0.1" targetFramework="net471" />
  <package id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="2.0.1" targetFramework="net471" />
  <package id="Microsoft.Configuration.ConfigurationBuilders.Azure" version="1.0.2" targetFramework="net471" />
  <package id="Microsoft.Configuration.ConfigurationBuilders.Base" version="1.0.1" targetFramework="net471" />
  <package id="Microsoft.IdentityModel.Clients.ActiveDirectory" version="3.14.2" targetFramework="net471" />
  <package id="Microsoft.Rest.ClientRuntime" version="2.3.8" targetFramework="net471" />
  <package id="Microsoft.Rest.ClientRuntime.Azure" version="3.3.7" targetFramework="net471" />
  <package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net471" />
  <package id="Modernizr" version="2.8.3" targetFramework="net471" />
  <package id="Newtonsoft.Json" version="12.0.2" targetFramework="net471" />
  <package id="WebGrease" version="1.6.0" targetFramework="net471" />
</packages>
Bmoe
  • 888
  • 1
  • 15
  • 37
  • You need to troubleshoot this: 1) Check to ensure that the certificate associated with the Service Principal 2) Please run Fiddler while executing the Web Form app locally. We need to see the responses from the webserver. – Matt Small Aug 18 '21 at 14:20
  • @MattSmall In Fiddler it says: `"{"error":{"code":"Unauthorized","message":"AKV10000: Request is missing a Bearer or PoP token."}}"` – Bmoe Sep 01 '21 at 15:28

1 Answers1

0

Solution 1 : use the service principal to access the keyvault, please follow the steps below

1.Register an application with Azure AD and create a service principal.

2.Get values for signing in and create a new application secret.

3.Navigate to the keyvault in the portal -> Access policies -> add the correct secret permission for the service principal

4.Then use the code below, replace the <client-id>, <tenant-id>, <client-secret> with the values got before.

using System;
using Microsoft.Azure.KeyVault;
using Microsoft.Azure.Services.AppAuthentication;

namespace test1
{
    class Program
    {
        static void Main(string[] args)
        {
            var azureServiceTokenProvider = new AzureServiceTokenProvider("RunAs=App;AppId=<client-id>;TenantId=<tenant-id>;AppKey=<client-secret>");
            var kv = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
            var secret = kv.GetSecretAsync("https://keyvaultname.vault.azure.net/", "mySecret123").GetAwaiter().GetResult();
            Console.WriteLine(secret);

        }
    }
}

enter image description here

For more details refer this thread

Solution 2: The error actually happens when the AzureServiceTokenProvider is trying to obtain a token to access the Vault using the service’s Managed Identity.

1.Pass “RunAs=App;” in the connectionString parameter of AzureServiceTokenProvider.

This way it will not try different modes to obtain a token, and the exception is a bit better.

2.Install/update the latest version of Microsoft.Azure.Services.AppAuthentication.

When you enable MSI and get access denied, check Azure keyvault Access policy>add access policy and add your MSI service principle with get secret permission. Refer to this article

Solution 3: add your App Service to your Key Vault's Access Policies.

  1. Navigate to the Key Vault resource in Azure Portal.

  2. I the menu, find Access Policies button and click on that.

  3. On the Access Policies page, find +Add Access Policy link and click.

  4. On the Add access policy page, Select all the permissions you want to grant to your App Service(probably all if you want to test this solution) and click on Select Principal

  5. In the principal window, search for your App Service using the App Service Name and Select.

  6. On the Add access policy page, click Add to add the policy to your Key Vault.

  7. Finally on the Access Policies page, click "Save" to save your changes. (a lot of people miss this step).

ShrutiJoshi-MT
  • 1,622
  • 1
  • 4
  • 9
  • The requirement is to use the Certificate with On-premise application. So cannot use Solution #2. Cannot use client secret because that's sensitive information anyway. It defeats the purpose of using KeyVault to store secrets if I have to end up storing a secret in the config file. I've only run into this problem of Certificate authentication not working when working in WebForms applications. My other applications it is working. So doesn't that suggest that Solution #3 is already satisfied? I could use Solution #1 if it involved finding the certificate in local machine. – Bmoe Aug 16 '21 at 12:50
  • Even in my .NET Core applications I am using same approach to find the certificate in local machine and then authenticate as SPN. The SPN has the same certificate added. – Bmoe Aug 16 '21 at 12:53
  • Can you please share test project if possible? And please check with this document https://learn.microsoft.com/en-us/azure/app-service/app-service-web-configure-tls-mutual-auth#special-considerations-for-certificate-validation – ShrutiJoshi-MT Aug 16 '21 at 13:03
  • how do you prefer I share the test project? – Bmoe Aug 16 '21 at 13:04
  • Also in the link you shared: https://learn.microsoft.com/en-us/azure/app-service/app-service-web-configure-tls-mutual-auth#aspnet-webforms-sample The ASP.NET WebForms sample has the authentication code in the `Page_Load` method. I have it configured in `web.config` instead though. Do i really need to authenticate on `Page_Load`? If so how do I override my `appSettings` and `connectionStrings` with values from KeyVault? – Bmoe Aug 16 '21 at 13:08