Dynamics 365 Auth Proxy Example

As mentioned on the Dynamics 365 for Operations Wiki OData Services, JSON-based Custom Service, and REST Metadata Service support standard OAuth 2.0 authentication.

Dynamics 365 for Operations currently supports Authorization code grant flow.

When i was writing this article, only one kind of application was supported:

Native client application – This flow uses a user name and password for authentication and authorization.

The Web Application (Confidential Client) integration is mentioned that it will be supported post-RTW but still haven't received any updates regarding this process.

Bellow is an Example on how you can create a generic Auth Proxy in order to integrate the OData Services from Dynamics 365 for Operations with any application.

I have chosen to create this example by using Azure API Apps for 3 main reasons:

  • It does not need to rely on your own servers
  • You can publish it in the same location as your Dynamics Environments
  • You can scale the service automatically based on usage.

Create a new Azure API App Project in your VS 2015 or 2013. Make sure that you have the latest Azure SDK installed.

Select Web -> ASP.NET Web Application

Select Azure API App and make sure the Host in the cloud option is checked.

On the next page you will be asked to enter the details for the Azure App Service:

Give your app a unique name or leave it as it is proposed by VS. Choose a subcription from the ones that you have on your account.
Create a new resource group and a new App service plan (or choose from existing one if you already have other Web Apps and want to have the proxy in the same group).

For Testing and even for production I suggest to start with a smaller service plan as you can always increase it afterwards if the API will have increased usage. You can check the Azure pricing page for additional details: Azure App Pricing. My personal option was to start with an S1 standard service plan.

After the project is created make sure you enable SSL on it :

You will notice that VS creates by default in APP_Start folder SwaggerConfig.cs . This is because Support for Swagger 2.0 API metadata is built into Azure App Service.

To Enable the Swagger UI in your project go to SwaggerConfig.cs and uncomment the following lines:

                            })
                        .EnableSwaggerUi(c =>
                            {

If you run your project and go to [PROJECT_URL]/swagger/ui/index you will see the Swagger interface and can play with your published controllers.

Now, let's create a new Model that we will use in the Auth Controller:

Right click on the Models folder -> Add -> New Item -> Class -> DYN365TokenRequest.cs

Replace the generated code with the following:

using System;  
using System.Collections.Generic;  
using System.ComponentModel.DataAnnotations;  
using System.Linq;  
using System.Web;

namespace Dyn365AuthProxy.Models  
{
    public class DYN365TokenRequest
    {
        [Required]
        public String Username { get; set; }

        [Required]
        public String Password { get; set; }

        [Required]
        public String ClientId { get; set; }

        [Required]
        public String ResourceURL { get; set; }
    }
}

Now that we created the model let's create the controller:

Right click on Controllers folder -> Add -> Controller -> Web Api 2 Controller - Empty

Name your controller:

Add the Active Directory Authentication Library (ADAL) using the NuGet Package manager console.

PM> Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory  

Replace the code generated in the DYN365TokenController.cs with the following:

using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Net;  
using System.Net.Http;  
using System.Web.Http;  
using Microsoft.IdentityModel.Clients.ActiveDirectory;  
using System.Threading.Tasks;  
using Dyn365AuthProxy.Models;

namespace Dyn365AuthProxy.Controllers  
{
    public class DYN365TokenController : ApiController
    {

        public async Task<IHttpActionResult> Post(DYN365TokenRequest tokenRequest)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            AuthenticationResult tokenResponse = null;

            //request token for the resource - which is the URI for your organization. NOTE: Important do not add a trailing slash at the end of the URI

            UriBuilder uri = new UriBuilder("https://login.windows.net/contoso2ax.onmicrosoft.com");

            AuthenticationContext authenticationContext = new AuthenticationContext(uri.ToString());

            UserPasswordCredential userCred = new UserPasswordCredential(tokenRequest.Username, tokenRequest.Password);

            try
            {
                tokenResponse = await authenticationContext.AcquireTokenAsync(tokenRequest.ResourceURL, tokenRequest.ClientId, userCred);
                authenticationContext.TokenCache.Clear();
            }
            catch (Exception x)
            {
                throw;
            }

            if (tokenResponse == null)
                return NotFound();
            else
                return Ok(tokenResponse);
        }

    }
}

Replace https://login.windows.net/contoso2ax.onmicrosoft.com with your Organization URI.

Now, let's build the project and test:

Go to [PROJECT_URL]/swagger/ui/index and now you will see that the new Controller is available:

Complete the token request parameter with the necessary info and click on try it out!

{
  "Username": "Dyn Username",
  "Password": "Dyn User Password",
  "ClientId": "string",
  "ResourceURL": "https://usnconeboxax1aos.cloud.onebox.dynamics.com"
}

In order to get the client ID you will need to register your application in Azure by using Register a native application with AAD guide.

You're response should look like :

{
  "AccessTokenType": "Bearer",
  "AccessToken": "",
  "ExpiresOn": "2016-12-25T13:48:17.8448867+00:00",
  "ExtendedExpiresOn": "2016-12-25T15:48:17.8448867+00:00",
  "ExtendedLifeTimeToken": false,
  "TenantId": "",
  "UserInfo": {
    "UniqueId": "",
    "DisplayableId": "",
    "GivenName": "",
    "FamilyName": "",
    "PasswordExpiresOn": null,
    "PasswordChangeUrl": null,
    "IdentityProvider": ""
  },
  "IdToken": ""
}

Now, let's test the token on a Dynamics 365 for Operations Environment (replace AccessToken with the value received before):

curl -X GET -H "Authorization: Bearer AccessToken" -H "Cache-Control: no-cache" "https://usnconeboxax1aos.cloud.onebox.dynamics.com/data/Customers?cross-company=true"

Your response should look like:

{
  "@odata.context": "https://usnconeboxax1aos.cloud.onebox.dynamics.com/data/$metadata#Customers",
  "value": []
}

Before publishing the APP to Azure to test it or use it outside of your DEV environment, i also recommend the following:

  • Redirect HTTP to HTTPS

In your web.config, in system.webServer section add the following:

<rewrite>  
      <rules>
        <rule name="Redirect HTTP to HTTPS">
          <match url="(.*)" />
          <conditions>
            <add input="{HTTPS}" pattern="off" ignoreCase="true" />
          </conditions>
          <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent"/>
        </rule>
      </rules>
</rewrite>  
  • Implement IP Restrictions

In your web.config, in system.webServer section add the following:

<security>  
      <ipSecurity allowUnlisted="false" denyAction="NotFound">
        <!-- complete ipAddress and subnetMask with Your public IP and Mask -->
        <add allowed="true" ipAddress="" subnetMask=""/>
        <!-- allow requests from the local machine -->
        <add allowed="true" ipAddress="127.0.0.1" subnetMask="255.255.255.255"/>
      </ipSecurity>
</security>  

You can now publish your APP to Azure. Right click on your Project and click Publish.

Depending of your DEV Enviroment you may encounter some Errors in parsing the rules above. Make sure you have the Rewrite and IP Restriction modules installed on your Local IIS and also, add the following to the .net config:

C:\Windows\System32\inetsrv>appcmd.exe set config "Default Web Site" -section:system.webServer/security/ipSecurity /overrideMode:Allow /commit:apphost