Search code examples
azuredynamics-crmazure-functions

Authenticate Azure Function App to connect to Dynamics 365 CRM online


Sometimes you need to know the answer to ask the right question, so I'm not sure if the title of this query is perfect. Anyway here goes.

I've developed an Azure Function App (time trigger based) to connect to Dynamics 365 online and do some work. All good! As this was a POC and I wanted to see what was possible, I wrote the following code.

        IServiceManagement<IOrganizationService> orgServiceManagement;
        orgServiceManagement = ServiceConfigurationFactory.CreateManagement<IOrganizationService>(new Uri(System.Environment.GetEnvironmentVariable("OrganizationService")));

        AuthenticationCredentials authCredentials = new AuthenticationCredentials();
        authCredentials.ClientCredentials.UserName.UserName = "[Non-interactive CRM Username here]";
        authCredentials.ClientCredentials.UserName.Password = "[Password here]";
        AuthenticationCredentials tokenCredentials;

        tokenCredentials = orgServiceManagement.Authenticate(authCredentials);

        OrganizationServiceProxy organizationProxy = new OrganizationServiceProxy(orgServiceManagement, tokenCredentials.SecurityTokenResponse);

My question... obviously now that the POC works I want to find a way to authenticate the Function App against Azure AD (instead of passing credentials in code) and get an access token that I can use to create my OrganisationServiceProxy, but how do I go about this. I cant seem to find a straight answer out there. Lots of architect-style answers that are way up in the clouds. I need developer-style answers (do this, then do that) :)

I'm sure a lot of newbie azure developers out there will find this useful to know. Thanks in advance.

Note for editors: This question isn't the same as Authenticate with Dynamics 365 from an Azure Function as I'm in the same tenant and subscription, using time triggers and not web hooks. My function app wakes up, connects to CRM, does some calculations, updates CRM and goes back to sleep.


Solution

  • I've managed to secure my credentials using the Azure Key Vault. For those newbies out there who are looking to do the same ... here are the steps.

    1. Login to the azure portal and create a key vault or if you already have one then go to the next step.
    2. Once the key vault is created, go to the secrets section of the Key Vault. You will now create a secret for each credential you need to secure. In my case I created a secret for the username and another for the password. Each time you create a secret, azure will issue you with a secret identifier. Make note of this as you will be later using this in your azure function config settings.
    3. Next you need to head over to Azure Active Directory (Azure AD). You need to head to App Registrations and create a new app registration. It doesnt matter at this point if you created your function app or not. This step is just about informing Azure AD that you have an application that you want to register so that it can issue you with an application ID. On creation of the app registration, you will need to make note of the application ID issued. Again you will use this in your function app config settings.
    4. Still in Azure AD, app registrations click on Keys and create a new key. Once you create a key, azure will provide you with the key value. (Please make note of this value as this is the only time Azure will show you this value.) You will need this app key too in your azure function app config settings.
    5. Head back to Azure Key Vault and to the Key Vault you created. This time click on Access Policies. What you are doing here is allowing your Azure AD registered function app to connect to this Key Vault. Click on Add New, Then select principal, find the app your registered with Azure AD (Do not select your function app which will also display here, you need to select the same name that you registered with Azure AD in step 3 above) Then under secret permissions, select 'Get' and click Save.
    6. Thats the setup done. The rest are code changes to make all of this work.
    7. Add the following using statements to the top of your code.

      using Microsoft.Azure.KeyVault;
      using Microsoft.IdentityModel.Clients.ActiveDirectory;

    8. If your function app code is in the Azure Portal, then add the following to your project.json file.

      { "frameworks": { "net46": { "dependencies": { "Microsoft.IdentityModel.Clients.ActiveDirectory": "3.13.4", "Microsoft.Azure.KeyVault": "2.0.1-preview", "Microsoft.AspNet.WebApi.Client": "5.2.3", "Microsoft.CrmSdk.CoreAssemblies": "9.0.0.7" } } } }

    9. If you are using Visual Studio, then you will need to ensure that you add the above references to your project.

    10. Please see my original post above to see how I was using credentials in code to how I've changed them now in the code below.

      AuthenticationCredentials authCredentials = new AuthenticationCredentials(); authCredentials.ClientCredentials.UserName.UserName = GetKVSecret("Secret1", log); authCredentials.ClientCredentials.UserName.Password = GetKVSecret("Secret2", log);

    11. And now here is the code for the GetKVSecret function.

      private static string GetKVSecret(string secretName, TraceWriter log)
      {
      var adClientId = System.Environment.GetEnvironmentVariable("AppADClientID");
      var adKey = System.Environment.GetEnvironmentVariable("AppADKey");
      var secret = System.Environment.GetEnvironmentVariable(secretName);
      
      var keyVault = new KeyVaultClient(async (string authority, string resource, string scope) => {
      var authContext = new AuthenticationContext(authority);
      var credential = new ClientCredential(adClientId, adKey);
      var token = await authContext.AcquireTokenAsync(resource, credential); 
      return token.AccessToken;
      });
      string returnValue;
      try
      {
          returnValue = keyVault.GetSecretAsync(secret).Result.Value;
          log.Info("Secret retrieved from Key Vault");
      }
      catch (Exception error)
      {
          log.Error("Unable to get secrets from Azure Key Vault.", error);
          throw;
      }
      return returnValue;
      

      }

    12. Last step, you can see that I'm picking up the AppADClientID and AppADKey from the config. So you will need to create the following entries in your app settings screen.
      AppADClientID: the value you got from step 3
      AppADKey: the value you got from step 4
      secret1: the value you got from step 2
      secret2: the value you got from step 2
      secret1 and 2 might vary depending on the number of secrets you created.

    So there! I hope you find this useful and if you have any queries please post them here, I'll try my best to answer them. I have to end by giving credit to the following resources which helped me along the way.

    Link 1 Link 2

    PS. This has been a pita to post the solution with code. Stackoverflow kept preventing me from submitting saying that I had code in the window that wasnt formatted correctly. However, I later realised it was the 'automatic' bullet formatting on the bullet points that was conflicting with the code inserts. Eitherway I think stack overflow should not prevent a post as it could mean content providers will give up in frustration (we have other paying jobs to do!)