0
votes

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;
    }
1

1 Answers

0
votes

When defining the connection in PingFed (defined from the above code as clientId = "120e36b3-0d1a-4596-a0d4-b31eb384607e"), you configure the Connection Type - Browser SSO Profiles, WS-Trust - you need to specify the protocol as SAML 2.0.