Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug] Certificate - Not able to retrieve token for user to call downstream graph api #219

Closed
2 of 8 tasks
TaranbirBhullar opened this issue Jun 18, 2020 · 4 comments
Closed
2 of 8 tasks
Assignees
Labels

Comments

@TaranbirBhullar
Copy link

TaranbirBhullar commented Jun 18, 2020

Which Version of Microsoft Identity Web are you using ?
Note that to get help, you need to run the latest version.
Microsoft.Identity.Web 0.1.5-preview

Where is the issue?

  • Web App
    • Sign-in users
    • Sign-in users and call web APIs
  • Web API
    • Protected web APIs (Validating tokens)
    • Protected web APIs (Validating scopes)
    • Protected web APIs call downstream web APIs
  • Token cache serialization
    • In Memory caches
    • Session caches
    • Distributed caches

Is this a new or existing app?
a. The app is in production, and I have upgraded to a new version of Microsoft Identity Web.
Our existing Appservice (Asp.net core application) uses MSI to connect to keyvault.
When we call upon TokenAquisition to GetAcccessTokenforUserAsync, we get a failure

Repro

    "ClientCertificates": [
      {
        "SourceType": "KeyVault",
        "KeyVaultUrl": "https://elixirdev.vault.azure.net/",
        "KeyVaultCertificateName": "ElixirDevApiCert"
      }
    ]
    "SendX5C": "true"
in our app startup
 ...           
services.AddProtectedWebApi(configuration)
       .AddProtectedWebApiCallsProtectedWebApi(configuration)
       .AddInMemoryTokenCaches();
...

When calling graph, we need to acquire token.
try {
var token = this.tokenAcquisition.GetAccessTokenForUserAsync(new string[] {
      "User.ReadBasic.All"
    })
}
catch (Exception e ){
Log(e);
}

Expected behavior
Get an access token for user to call graph.

Actual behavior
Exception is thrown
DefaultAzureCredential authentication failed.

Additional context/ Logs / Screenshots
Stack Trace

{
  "exception": " at Azure.Identity.DefaultAzureCredential.GetTokenAsync(Boolean isAsync, TokenRequestContext requestContext, CancellationToken cancellationToken)
   at Azure.Identity.DefaultAzureCredential.GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
   at Azure.Security.KeyVault.ChallengeBasedAuthenticationPolicy.AuthenticateRequestAsync(HttpMessage message, Boolean async, AuthenticationChallenge challenge)
   at Azure.Security.KeyVault.ChallengeBasedAuthenticationPolicy.ProcessCoreAsync(HttpMessage message, ReadOnlyMemory`1 pipeline, Boolean async)
   at Azure.Core.Pipeline.TaskExtensions.EnsureCompleted(ValueTask task)
   at Azure.Security.KeyVault.ChallengeBasedAuthenticationPolicy.Process(HttpMessage message, ReadOnlyMemory`1 pipeline)
   at Azure.Core.Pipeline.HttpPipelinePolicy.ProcessNext(HttpMessage message, ReadOnlyMemory`1 pipeline)
   at Azure.Core.Pipeline.RetryPolicy.ProcessAsync(HttpMessage message, ReadOnlyMemory`1 pipeline, Boolean async)
   at Azure.Core.Pipeline.RetryPolicy.ProcessAsync(HttpMessage message, ReadOnlyMemory`1 pipeline, Boolean async)
   at Azure.Core.Pipeline.TaskExtensions.EnsureCompleted(ValueTask task)
   at Azure.Core.Pipeline.RetryPolicy.Process(HttpMessage message, ReadOnlyMemory`1 pipeline)
   at Azure.Core.Pipeline.HttpPipelinePolicy.ProcessNext(HttpMessage message, ReadOnlyMemory`1 pipeline)
   at Azure.Core.Pipeline.HttpPipelineSynchronousPolicy.Process(HttpMessage message, ReadOnlyMemory`1 pipeline)
   at Azure.Core.Pipeline.HttpPipelinePolicy.ProcessNext(HttpMessage message, ReadOnlyMemory`1 pipeline)
   at Azure.Core.Pipeline.HttpPipelineSynchronousPolicy.Process(HttpMessage message, ReadOnlyMemory`1 pipeline)
   at Azure.Core.Pipeline.HttpPipeline.Send(HttpMessage message, CancellationToken cancellationToken)
   at Azure.Core.Pipeline.HttpPipeline.SendRequest(Request request, CancellationToken cancellationToken)
   at Azure.Security.KeyVault.KeyVaultPipeline.SendRequest(Request request, CancellationToken cancellationToken)
   at Azure.Security.KeyVault.KeyVaultPipeline.SendRequest[TResult](RequestMethod method, Func`1 resultFactory, CancellationToken cancellationToken, String[] path)
   at Azure.Security.KeyVault.Certificates.CertificateClient.GetCertificate(String certificateName, CancellationToken cancellationToken)
   at Microsoft.Identity.Web.DefaultCertificateLoader.LoadFromKeyVault(String keyVaultUrl, String certificateName)
   at Microsoft.Identity.Web.DefaultCertificateLoader.LoadIfNeeded(CertificateDescription certificateDescription)
   at Microsoft.Identity.Web.DefaultCertificateLoader.LoadFirstCertificate(IEnumerable`1 certificateDescription)
   at Microsoft.Identity.Web.TokenAcquisition.BuildConfidentialClientApplicationAsync()
   at Microsoft.Identity.Web.TokenAcquisition.GetOrBuildConfidentialClientApplicationAsync()
   at Microsoft.Identity.Web.TokenAcquisition.GetAccessTokenForUserAsync(IEnumerable`1 scopes, String tenant)
   at Microsoft.Azure.Elixir.Framework.Data.External.GraphDataProvider.GetAccessToken() 

Prior to using this nuget package. I had modified the library and used it like below.

// create cert, obtained from keyvault
SecretBundle certificateSecret = new KeyVaultClient(
                    new KeyVaultClient.AuthenticationCallback(
                    new AzureServiceTokenProvider().KeyVaultTokenCallback))
                    .GetSecretAsync(vaultBaseUrl, secretName)
                    .ConfigureAwait(false)
                    .GetAwaiter()
                    .GetResult();
      byte[] privateKeyBytes = Convert.FromBase64String(certificateSecret.Value);
      x509Certificate2 = new X509Certificate2(
             privateKeyBytes,
             (string)null,
             X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.EphemeralKeySet);

The storageFlags are used since we don't have a user profile (available in appservice), and we store the token privately on the running app.

Taken from the Azure Appservice documentation
If you are deploying an ASP.NET Core application to be used with IIS, you will need to make sure to load the user profile otherwise you will get a cryptography exception. If you are using Azure WebApps, you can add the app setting WEBSITE_LOAD_USER_PROFILE with value 1 to load the user profile. A certificate that has a private key requires user profile and, by default, an Azure WebApp doesn’t create the user profile. This is only supported for Standard App Service plans and above

We create a clientConfidentialApplication with the provided cert above and acquire token as below

await application
     .AcquireTokenSilent(scopes.Except(this._scopesRequestedByMsal), account)
     .WithSendX5C(true)
     .ExecuteAsync()
     .ConfigureAwait(false)

This had worked well for us. We are now looking to leverage the capabilities provided by Microsoft.Identity.Web. Any guidance on the above will be appreciated

@jennyf19
Copy link
Collaborator

@TaranbirBhullar fix has been merged into master. Thanks for providing so much context into the issue. will be in our next release.

@jennyf19 jennyf19 self-assigned this Jun 24, 2020
@jennyf19
Copy link
Collaborator

Included in 0.2.0-preview release

@TaranbirBhullar
Copy link
Author

TaranbirBhullar commented Jul 18, 2020

Thank you for the changes, really appreciate the work done here. I can verify Microsoft.Identity.Web works for both Service fabric and our App Services in Azure.

I do want to make a note here that. This solution was not the exact solution to the problem posted above.

Our environment was using a User-assigned MSI, which the underlying DefaultAzureCredential is not able to pick up.
DefaultAzureCredential can easily obtain a System-assigned MSI.

The actual fix was to set Environment Variable AZURE_CLIENT_ID to be the User-assigned MSI's client ID

If you want to do this through the Azure portal.
You can head over to your Azure App Service -> Settings | Configuration -> Application Settings
Add or update the AZURE_CLIENT_ID app setting to the User assigned MSI's client ID.

@jennyf19 and @jmprieur Please add this to any FAQ for users that are using user-assigned MSI.

@jmprieur
Copy link
Collaborator

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants