Connecting to Dynamics 365 using various grant types
Summary
As of April 2022, most of the methods below have been deprecated by Microsoft. Connecting to Microsoft via a console application now requires an Azure App registration.
To connect to Microsoft Datavaerse via Azure Functions, click here: https://eax360.com/call-dataverse-api-from-within-an-azure-function/
There are multiple methods for connecting to Microsoft Dynamics from a command-line application.
I have added some deprecated/unsupported methods for connecting to Dynamics 365 Customer Engagement for historic context.
Using AuthType Client Secret
- Register a new Azure Application.
- Provide the API permissions as appropriate.
- Generate secret.
- Make a note of the clientid, secret, and tenantid.
public static string _org = "https://acme.crm11.dynamics.com";
public static string _clientId = "e0fff54f-54d2-45b6-b82a-084320087";
public static string _clientSecret = "f347Q~j5hF0a7jdhcbshs73js,g0gsf33";
internal static IOrganizationService ConnectSource()
{
var connectionString = $@"AuthType=ClientSecret; url={_org}; ClientId={_clientId}; ClientSecret={_clientSecret}";
using (var svc = new CrmServiceClient(connectionString))
{
WhoAmIRequest request = new WhoAmIRequest();
WhoAmIResponse response = (WhoAmIResponse)svc.Execute(request);
Console.WriteLine("Your UserId is {0}", response.UserId);
}
CrmServiceClient connn = new CrmServiceClient(connectionString);
CrmServiceClient.MaxConnectionTimeout = new TimeSpan(60, 60, 60);
return connn;
}
Using the OrganizationServiceProxy (Uses WS-Trust. Deprecated)
Note that this method has been deprecated as of 2021. The method uses an un-secure approach to storing credentials.
using System;
using System.Linq;
using Microsoft.Xrm.Sdk.Client;
using System.ServiceModel.Description;
using Microsoft.Crm.Sdk.Messages;
namespace Carl.Crm.OrgServiceProxy
{
class Program
{
static void Main(string[] args)
{
try
{
Uri oUri = new Uri("https://yourcrm.crm.dynamics.com/XRMServices/2011/Organization.svc");
ClientCredentials clientCredentials = new ClientCredentials();
clientCredentials.UserName.UserName = "your@email.com";
clientCredentials.UserName.Password = "yourpassword";
OrganizationServiceProxy _serviceProxy = new OrganizationServiceProxy(oUri, null, clientCredentials, null);
_serviceProxy.EnableProxyTypes();
OrganizationServiceContext orgContext = new OrganizationServiceContext(_serviceProxy);
RetrieveVersionRequest versionRequest = new RetrieveVersionRequest();
RetrieveVersionResponse versionResponse = (RetrieveVersionResponse)_serviceProxy.Execute(versionRequest);
Console.WriteLine("Microsoft Dynamics CRM version {0}.", versionResponse.Version);
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
Console.ReadLine();
}
}
}
Using the OrganizationService (Uses WS-Trust. Deprecated)
Note that this method has been deprecated as of 2021. The method uses an un-secure approach to storing credentials.
using Microsoft.Xrm.Client;
using Microsoft.Xrm.Client.Services;
using Xrm;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Metadata;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CRM_Console_App
{
class Program
{
static void Main(string[] args)
{
//Simplified Connection: https://msdn.microsoft.com/en-us/library/gg695810.aspx
CrmConnection crmConnection = CrmConnection.Parse("Url=http://crm/beta; Domain=MyDomainName; Username=Administrator; Password=MyPassword;");
OrganizationService service = new OrganizationService(crmConnection);
XrmServiceContext context = new XrmServiceContext(service);
var allisonBrown = new Xrm.Contact
{
FirstName = "Allison",
LastName = "Brown",
Address1_Line1 = "23 Market St.",
Address1_City = "Sammamish",
Address1_StateOrProvince = "MT",
Address1_PostalCode = "99999",
Telephone1 = "12345678",
EMailAddress1 = "allison.brown@example.com"
};
context.AddObject(allisonBrown);
context.SaveChanges();
}
}
}
Token exchange
For the legacy Dynamics CRM code that uses Microsoft.Xrm.Client
and OrganizationService
, you can now use APIM policies to intercept the existing credentials and obtain an OAuth 2.0 token from Azure AD. This process involves replacing the old authentication method (which uses Username
and Password
) with a modern OAuth 2.0 token by intercepting the request in APIM.
In the inbound policy of APIM, you will need to request a new token from Azure AD, insert the token into the Authorization
header, and forward the request to the downstream Dynamics 365 API. Here is an example policy:
Code Example for APIM Token Exchange:
<inbound>
<!-- Extract username and password if needed -->
<set-variable name="username" value="@(context.Request.Headers.GetValueOrDefault('Username', ''))" />
<set-variable name="password" value="@(context.Request.Headers.GetValueOrDefault('Password', ''))" />
<!-- Request OAuth token from Azure AD -->
<send-request mode="new" response-variable-name="tokenResponse" timeout="20">
<set-url>https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token</set-url>
<set-method>POST</set-method>
<set-header name="Content-Type" exists-action="override">
<value>application/x-www-form-urlencoded</value>
</set-header>
<set-body>@{
return $"client_id={client_id}&client_secret={client_secret}&scope={scope}&grant_type=client_credentials";
}</set-body>
</send-request>
<!-- Extract token from response -->
<set-variable name="accessToken" value="@(tokenResponse.Body.As<JObject>()["access_token"].ToString())" />
<!-- Set the Authorization header with the new OAuth token -->
<set-header name="Authorization" exists-action="override">
<value>Bearer @{accessToken}</value>
</set-header>
</inbound>
Using this approach, the existing legacy credentials (Username
and Password
) are captured (if needed), and APIM performs a token exchange by calling Azure AD to retrieve a valid OAuth 2.0 token. The legacy CRM request is modified with the new token, and the request proceeds to Dynamics 365. This allows your legacy CRM console app to interact with Dynamics 365 securely, without changing the original code.
Using Xrm.tooling v8.1 D365 (Deprecated)
Works for Dynamics 365 Online (period of 2018 & 2019) version 8.1.
Note that this method has been deprecated as of 2021. The method uses an un-secure approach to storing credentials.
using Microsoft.Xrm.Tooling.Connector;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Metadata;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Dynamics365.Connections
{
class Program
{
static void Main(string[] args)
{
try
{
var connectionString = @"AuthType = Office365;
Url = https://[org].crm4.dynamics.com/;Username=[username];Password=[password]";
CrmServiceClient conn = new CrmServiceClient(connectionString);
IOrganizationService service;
service = (IOrganizationService)conn.OrganizationWebProxyClient != null ? (IOrganizationService)conn.OrganizationWebProxyClient : (IOrganizationService)conn.OrganizationServiceProxy;
RetrieveEntityRequest retrieveEntityRequest = new RetrieveEntityRequest
{
EntityFilters = EntityFilters.All,
LogicalName = "account"
};
RetrieveEntityResponse retrieveAccountEntityResponse = (RetrieveEntityResponse)service.Execute(retrieveEntityRequest);
EntityMetadata AccountEntity = retrieveAccountEntityResponse.EntityMetadata;
Console.WriteLine("Account entity metadata:");
Console.WriteLine(AccountEntity.SchemaName);
Console.WriteLine(AccountEntity.DisplayName.UserLocalizedLabel.Label);
Console.WriteLine(AccountEntity.EntityColor);
Console.WriteLine("Account entity attributes:");
foreach (object attribute in AccountEntity.Attributes)
{
AttributeMetadata a = (AttributeMetadata)attribute;
Console.WriteLine(a.LogicalName);
}
Console.ReadLine();
}
catch (Exception ex)
{ }
}
}
}
Using Xrm.tooling v9.1 D365 (Deprecated)
The snippet illustrates connecting to an on-premise environment. Other authentication types can be found here:
AuthenticationType or AuthType | Specifies the authentication type to connect to Dynamics 365 Customer Engagement (on-premises). Valid values are: AD , IFD (AD FS enabled), OAuth , or Office365 .– AD and IFD are permitted for Dynamics 365 Customer Engagement (on-premises) instances only.– OAuth , Certificate , and ClientSecret are permitted for Dynamics 365 Customer Engagement (on-premises) and Common Data Service instances. For on-premises, ADFS 3.x+ and App\Client Id registration with ADFS is required for OAuth , Certificate and ClientSecret types.– Office365 is permitted for Common Data Service instances only. |
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Metadata;
using Microsoft.Xrm.Tooling.Connector;
using System;
using System.Net;
namespace Dynamics365.Connections
{
internal class Program
{
private static void Main(string[] args)
{
try
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
string envcon = @"AuthType = AD; Url = http://192.168.0.30/exp01/;Domain=cedomain;Username=serviceaccount;Password=CC%******";
CrmServiceClient conn = new CrmServiceClient(envcon);
//var service = (IOrganizationService)conn.OrganizationWebProxyClient ?? conn.OrganizationServiceProxy;
IOrganizationService service;
service = (IOrganizationService)conn.OrganizationWebProxyClient != null ? (IOrganizationService)conn.OrganizationWebProxyClient : (IOrganizationService)conn.OrganizationServiceProxy;
RetrieveEntityRequest retrieveEntityRequest = new RetrieveEntityRequest
{
EntityFilters = EntityFilters.All,
LogicalName = "account"
};
RetrieveEntityResponse retrieveAccountEntityResponse = (RetrieveEntityResponse)service.Execute(retrieveEntityRequest);
EntityMetadata AccountEntity = retrieveAccountEntityResponse.EntityMetadata;
Console.WriteLine("Account entity metadata:");
Console.WriteLine(AccountEntity.SchemaName);
Console.WriteLine(AccountEntity.DisplayName.UserLocalizedLabel.Label);
Console.WriteLine(AccountEntity.EntityColor);
Console.WriteLine("Account entity attributes:");
foreach (object attribute in AccountEntity.Attributes)
{
AttributeMetadata a = (AttributeMetadata)attribute;
Console.WriteLine(a.LogicalName);
}
Console.ReadLine();
}
catch (Exception ex)
{ }
Console.ReadLine();
}
}
}
Token Exchange using Ocelot & Owin
To implement a token exchange in Ocelot with OWIN, create a custom delegating handler in Ocelot to intercept the request, obtain the token from Azure AD, and inject it into the Authorization
header before forwarding it.
public class TokenExchangeHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var token = await GetOAuthTokenAsync(); // Implement Azure AD token request
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
return await base.SendAsync(request, cancellationToken);
}
private async Task<string> GetOAuthTokenAsync()
{
// Implement Azure AD token request logic
}
}
In ocelot.json
, register the handler:
{
"Routes": [
{
"DownstreamPathTemplate": "/api/{everything}",
"UpstreamPathTemplate": "/api/{everything}",
"DelegatingHandlers": [ "TokenExchangeHandler" ]
}
]
}
Use OWIN middleware to intercept the request, perform token exchange, and pass the modified request to downstream services. Ocelot handles the routing while OWIN manages the request processing.
Azure Functions
If you are using Azure Functions to handle the token exchange, here’s how you can do it:
- Create an Azure Function that receives the request from your legacy service.
- In the function, request a new OAuth token from Azure AD using client credentials, and inject this token into the request headers.
- Forward the modified request to Dynamics 365.
[FunctionName("TokenExchangeFunction")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req)
{
string newToken = await GetOAuthTokenFromAzureAD();
// Forward the request to Dynamics 365 with the new token
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://your-dynamics-instance/api/data/v9.0/endpoint");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", newToken);
var response = await client.SendAsync(request);
return new OkObjectResult(await response.Content.ReadAsStringAsync());
}
private static async Task<string> GetOAuthTokenFromAzureAD()
{
// Implement the OAuth token retrieval from Azure AD
}