I am attempting to connect to a Dynamics 365 online instance utilising a third party identity provider, PingFed. I am not responsible for the configuration of this identity provider and do not have access to any of its settings.
Using the code below, I am able to identify the authentication provider and successfully authenticate, however, once we are redirected to the Microsoft login services, an error is thrown, which I have captured via Fiddler. When looking at the SAML assertion, it is SAML 1.1.
The error we receive is:
The request is not a valid SAML 2.0 protocol message
If I authenticate using the interactive prompt, I am able to successfully log in. This won't meet our needs as we need to have a service account authenticate to the CRM.
Why does it work when we log in interactively but not when we attempt via code?
The team responsible for managing authentication are unable to assist so I was hoping some one could shed some light on what is going on?
public static void OverrideExample(bool usePrompt = false)
{
try
{
// Define organization Url
Uri orgUrl = new Uri("https://myorg.crm6.dynamics.com");
bool usePrompt = false;
// Call your existing authentication implementation
AuthenticationResult accessToken = MichelOverrideExampleHookImplementation.GetAccessTokenFromAzureAD(orgUrl, usePrompt);
// Create instance of your hook
var hook = new MichelOverrideExampleHookImplementation();
// Add token to your hook
hook.AddAccessToken(orgUrl, accessToken);
// Register the hook with the CrmServiceClient
CrmServiceClient.AuthOverrideHook = hook;
// Create a new instance of CrmServiceClient, pass your organization url and make sure useUniqueInstance = true!
var client = new CrmServiceClient(orgUrl, useUniqueInstance: true);
if (!client.IsReady)
{
// Connection failed, report error
Console.Error.WriteLine(client.LastCrmException?.Message ?? client.LastCrmError);
}
else
{
// Connection success
// TODO Add your code here
var qry = new QueryExpression("account");
qry.ColumnSet = new ColumnSet(true);
var results = client.RetrieveMultiple(qry);
Console.WriteLine("Connection Succesfull!");
}
}
catch (Exception ex)
{
Console.WriteLine("Exception: " + ex.Message);
}
}
}
public class MichelOverrideExampleHookImplementation : IOverrideAuthHookWrapper
{
// In memory cache of access tokens
Dictionary<string, AuthenticationResult> accessTokens = new Dictionary<string, Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationResult>();
public void AddAccessToken(Uri orgUri, Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationResult accessToken)
{
// Access tokens can be matched on the hostname,
// different endpoints in the same organization can use the same access token
accessTokens[orgUri.Host] = accessToken;
}
public string GetAuthToken(Uri connectedUri)
{
// Check if you have an access token for this host
if (accessTokens.ContainsKey(connectedUri.Host) && accessTokens[connectedUri.Host].ExpiresOn > DateTime.Now)
{
return accessTokens[connectedUri.Host].AccessToken;
}
else
{
accessTokens[connectedUri.Host] = GetAccessTokenFromAzureAD(connectedUri);
}
return null;
}
public static AuthenticationResult GetAccessTokenFromAzureAD(Uri orgUrl,bool usePrompt = false)
{
string resource = "https://myorg.crm6.dynamics.com";
AuthenticationParameters ap = AuthenticationParameters.CreateFromResourceUrlAsync(
new Uri("https://myorg.crm6.dynamics.com/api/data/")).Result;
String authorityUrl = ap.Authority;
String resourceUrl = ap.Resource;
// TODO Substitute your correct CRM root service address,
// TODO Substitute your app registration values that can be obtained after you
// register the app in Active Directory on the Microsoft Azure portal.
string clientId = "120e36b3-0d1a-4596-a0d4-b31eb384607e";
string redirectUrl = "app://myredirecturl";
var userCred = new UserCredential("[email protected]", "password");
// Authenticate the registered application with Azure Active Directory.
//Previous authority urls: https://login.windows.net/common
AuthenticationContext authContext = new AuthenticationContext(authorityUrl, false);
AuthenticationResult result = null;
if (usePrompt)
{
result = authContext.AcquireToken(resource, clientId, new Uri(redirectUrl));
}
else
{
result = authContext.AcquireToken(resourceUrl, clientId, userCred);
}
return result;
}