Tuesday, November 15, 2011

Trusted Intermediary in AIF Services

The use of WCF as the platform for AIF in AX 2012 makes integrating relatively simple. Most recent products and programming languages support SOAP or REST services, or Odata. There is one big catch with the services in AX however… users need to be authenticated by an active directory! Obviously, this is a stumble block when considering mobile devices or cloud connections… Active Directory (AD) is usually hosted on premise behind a firewall, mobile devices probably don't support AD authentication, and AX AIF does not support basic HTTP authentication. On top of that, in case of public services, you wouldn't want to have to add every single user to your active directory! Fortunately, AX 2012 supports a new model, which is used for both AIF and EP, called the Trusted Intermediary.

The phrase "trusted intermediary" means you indicate that for a particular service you trust certain users or user groups enough that you allow them to impersonate another user. Basically, you trust the intermediary to have done the user authentication instead of AX. This opens up the possibility of authenticating users in your own app and, if successful, call into AX as a trusted intermediary user and run code as that authenticated user. For example, if you are building a Windows Azure site or service, you can use the built-in Azure frameworks to authenticate users against LiveId, OpenId, etc. Once authenticated you can pass that user's ID to AX and AX will assume that you, as the trusted intermediary, have successfully authenticated and verified the user's identity.

One could now ask, why would you need all of this? Can't we just have one valid user in AX and run our service as that user? Well of course that works, but you would not have any of the AX security features available. Basically, your intermediary user in such a case would need administrator privileges (to support any and all requests that it could possibly get), and then YOUR intermediary service would need to filter out actions or data that the end user is not supposed to see. A lot of work, and lot of potential for security holes in your own app. With a trusted intermediary impersonating another user, AX can use the impersonated user's AX security settings and enforce them, including XDS for example!

Of course the term intermediary implies there is something in between the end user and AX. When building a website, this is obvious, the website (which can run in an application pool identity that is a trusted intermediary) can impersonate the end user and is the intermediary between user and AX. This is how Enterprise Portal on SharePoint 2010 works with claims users. For AIF however, what does this mean? Well, it means you would need to build a service in between AIF and your end user or consuming app. We will cover the code for that in a follow-up article. In this article, I will show you how easy it is to set this up, and how to then consume this service with impersonation.

For once we will not be going into the developer workspace of AX. All we need to do is some basic setup for AIF. We'll create an enhanced port, add some service operations, and select an intermediary user. We'll use NETTCP so we won't need the AIF IIS components installed.
Open the inbound port definitions in the AX menu under System Administration > Setup > Services and Application Integration Framework > Inbound ports.


Click the NEW button to create a new enhanced port, and give it a name and description. Click on the "Service operations" button. Find the operations that start with "InventItemService." and add then all to the list of operations for your port by clicking the "<" button in the middle. Close the screen.








Back on the inbound port screen, check the "Allow trusted intermediary to impersonate" and click the button that becomes available called "Trusted intermediary users". For the purpose of this article, select "User" as the user type, and select your current user.



Click close on the intermediary users screen. Under the troubleshooting fast tab, enable "Include exceptions in fault". This will output AX exceptions into the service exception, so your consuming app will actually get the AX exception message (such as "access denied" or any other exception that may get thrown). You can also enable logging if you want, I set my logging mode to "All document versions".


As far as the inbound port is concerned, everything is done. Click "Activate" to activate this new port. This will generate the necessary IL code and enable the port.


Now that the port is activated, the URL for the WSDL will appear on the screen. Write this down or copy to clipboard, we will need this later on to test the port!


So, since we are assumed to be authenticating users outside of AX, we still need a way to setup valid AX users that don't actually have a domain login. Go to System Administration > Common > Users and click the "User" button to create a new user.


In the "User ID" field you give the user a unique AX user id name or number. You are restricted to 8 characters there. For the "Account Type", select "Claims user" (this indicates a non-domain user outside of AX!). The User Name field is not really important for the authentication process, but this should be the full name. Set the default company for the user. The two remaining fields, "Network domain" and "Alias" are the important ones. The network domain field for a claims user needs to be filled out, and you should put in the name of the authentication provider. It sounds more like a best practice though, you can really fill in whatever you want. For the alias, this is the actual username that will be used to log on. For a lot of authentication services these days, an email address is used, so I decided for this example to use network domain (aka provider) "DaxMusingsAuth" and alias "ClaimGuy@MyApp.com" (being fully aware email addresses are not case sensitive, but it looks nice, don't you think?).


After (!) the fields are field in, the only thing that remains is to enable the user!


The observant reader my have noticed I did not assign any roles to this claims user. And in fact, the sneaky blogger that I am, I did this on purpose! Your trusted intermediary (yourself) will have access to read the item, but this new claims user will not... This will show our calling code is actually executed as the claims user, and I also hope that proves the point that using claims users instead of just one administrator user, has clear security benefits.

So, to test this out, we'll create a quick console app and pretend to be ClaimGuy@MyApp.com. Open Visual Studio 2010 and create a new project of type Console Application.


In your solution explorer, right-click the "References" node and select "Add Service Reference". Enter or paste in the WSDL url from the inbound port we created earlier, and click "Go". As a Namespace I decided to put in "ItemStuff".


Click OK to generate the service proxies. Almost there! First we'll ask the user to enter his username, next to enter an item number to lookup.
Console.Write("Enter username: ");
            string username = Console.ReadLine();
            Console.Write("Enter item number to look up: ");
            string itemnumber = Console.ReadLine();


Next, we'll create our service client to call the service, and provide the call context. The call context is where the magic happens! We specify the company, a unique message id for this call into AX (just create a quick GUID on the fly), and we pass in the user we wish to impersonate, in this case the username that was entered on the console. Note that similar to an AD username, we need to provide the provider name we filled out in the "network domain" field (I used DAXMusingsAuth), a backslash (escaped inside a string so \\ in this case), and then the username.
ItemStuff.ItemServiceClient client = new ItemStuff.ItemServiceClient();
            ItemStuff.CallContext context = new ItemStuff.CallContext();

            context.Company = "CEU";
            context.MessageId = Guid.NewGuid().ToString();
            context.LogonAsUser = String.Format("DAXMusingsAuth\\{0}", username);


Next we fill out the entitykeylist for the item we want to lookup, and call the service, outputting any exceptions to the console. I call ReadLine() at the end to prevent the console app from closing right away.

ItemStuff.EntityKey[] entityKey = new ItemStuff.EntityKey[1];
            entityKey[0] = new ItemStuff.EntityKey();
            entityKey[0].KeyData = new ItemStuff.KeyField[1];
            entityKey[0].KeyData[0] = new ItemStuff.KeyField();
            entityKey[0].KeyData[0].Field = "ItemId";
            entityKey[0].KeyData[0].Value = itemnumber;

            try
            {
                ItemStuff.AxdItem item = client.read(context, entityKey);

                if (item.InventTable.Length > 0)
                {
                    Console.WriteLine(item.InventTable[0].NameAlias);
                }
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            Console.ReadLine();


To see the full code, click here. That's it! Run the code, enter username claimguy@myapp.com and see what happens!


You will actually get a message that the access is denied. Feel free to try a non-existent username, and you should see a "logon failed" message.

If you want to see this work for sure, feel free to add your claims user to the system administrator or other role with access. Here's my output:


If you enabled logging on your inbound port, you can check the logs in System Administration > Periodic > Services and Application Integration Framework > History and on the "Details" tab you will notice the "Endpoint user" showing your claims user id, and the "Submitting user" showing the trusted intermediary user.

30 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Joris, how do you set this up when you are calling in from outside the network such as from Azure? Do you set a claim user as the trusted intermediary?

    ReplyDelete
    Replies
    1. Azure has a few different options. I don't think you can make a claim user the trusted intermediary, since the trusted intermediary is used to support the claim users. But you could just follow this article and have Azure call into the webservice using any user you want...

      Alternatively, which I haven't played with, you can use the Azure service bus, so you wouldn't even need to expose the webservice on the internet at all. The way you authenticate/login would stay the same, but the benefit is not having to expose your service to the internet, but still be able to connect Azure to it (magic!). This is what Microsoft uses for RapidStart services, for example.

      Delete
  3. Thanks Mate...Excellent article...

    I created a AIF webservice to import free text invoice from CRM. And was successful in doing it as well until I tried calling the web service from a different server... and BANG!!! That was when I got hit by logon failure.

    Your article really helped me to resolve my problem.
    Cheers Mate...Keep up the good work

    ReplyDelete
  4. Hi, when i run main method i get this error "Object reference not set to an instance of an object". Can you help me?

    ReplyDelete
    Replies
    1. You should be able to debug that program and find out what object is not instantiated. Also look at any inner exception inside the exception thrown.

      Delete
  5. Nice article.

    Can I some how do the same for AX 2009? Or do I have to write a interface between AIF and the end user APP.

    ReplyDelete
    Replies
    1. AX 2009 does not have the trusted intermediary built-in. So you'll have to create an interface in between, which takes your intermediary logins but then actually makes the call to AX using an actual (AD) AX login.

      Delete
  6. Hi,

    I have an AIF Service with httpbinding. When adding a proxy service reference to it from the same machine it works fine,
    But when trying to add service reference from a different machine I am getting a series of prompts for credentials.
    Any ideas how to fix this?

    ReplyDelete
    Replies
    1. Sometimes security policies can be set to require prompt for credentials, much like internet explorer options where you can select integrated authentication or not. It may have something to do with that.
      I would suggest checking some WCF forums and ask there. Try http://social.microsoft.com/Forums/en-US/categories or http://stackoverflow.com

      Delete
  7. hi,

    I got "access denied" message after follow all the step. i also add system administrator role to the user but still have the same result.. could you please suggest what i'm doing wrong please. Tq

    ReplyDelete
  8. Hi,

    on my side, we already had a working web service mounted like the one you show us. But, since we installed some hotfix, the service does'nt work anymore. We try to apply the CU7 beta to see if it works, but we have the same errors. In event viewer of the IIS server, we have these 2 errors:

    The received message has no security context.
    The logon user could not be determined. An exception will be thrown: The request could not be authenticated.

    We are at the point to contact Microsoft for some support, but you seems to know your thing pretty well so I take a chance if you have some idea.

    ReplyDelete
    Replies
    1. Not sure what that is, haven't seen it. I would consider removing the AIF web components for IIS and reinstalling those.

      Delete
    2. it has been done many time and it didn't work...

      Delete
    3. No ideas here. I think you're on track by contacting Microsoft support.

      Delete
    4. Just to let you know that Microsoft had tell us that the anonymous authentication to the AIF service is not supported anymore since AX 2012 R2 CU6. So, we have to change our things...

      Delete
    5. Hi Pascal, can you share what did you change?

      Delete
  9. Joris, great post!

    One question: If I want to connect my service using on LogonAsUser property my Business Connector Proxy Account, how do I do that?

    Thank you.

    ReplyDelete
  10. Joris,

    Thank you very much...!! I am using

    ReplyDelete
  11. Excellent post Joris,
    maybe we can add that all of this is also working cross domain wihen you set the required information in the service client :
    client.ClientCredentials.Windows.ClientCredential.Domain = "DAXMusingsAuth";
    client.ClientCredentials.Windows.ClientCredential.UserName = "Admin";
    client.ClientCredentials.Windows.ClientCredential.Password = "XXXXXXX";

    ReplyDelete
    Replies
    1. Great feedback. I know of this feature but I thought I had trouble getting that to work initially. Then again, this article was quite a while back and don't remember exactly.

      Thanks for posting this!

      Delete
  12. Great work!!! I can solve my problem whe I deployed my Web Application by IIS.

    ReplyDelete
  13. Hi Joris,

    Great post! Do you know if it is possible to do this with the standard query service of Odata query service?

    Rob

    ReplyDelete
    Replies
    1. Well, those services don't go through the AIF setup so you don't have these options to enable the trusted intermediary (you have to selected the trusted users on a per-service basis as shown in the blog post above). I have to admit I haven't used or tested this feature in R2 or R3 (this article was written in November of 2011) but I know I haven't seen any new setups on the "data sources" side for query services.

      Delete
  14. Hi Joris,

    Thanks for the awsome article. But this things not worked fine in AX 2012 R3 CU8

    Do you have any idea to do the same in AX 2012 R3 CU8?

    Regards,
    Janak

    ReplyDelete
  15. The Server rejected the client credentials. SSPI failed error.
    Please suggest the solution.

    ReplyDelete
    Replies
    1. Hi,
      I am facing the same problem,
      please suggest the solution ASAP.

      Delete
    2. I've seen the SSPI error a few times. If you search the internet for it, you'll find these are standard WCF types of issues, unrelated to AX.
      What I've seen to be an issue sometimes, is one of two things:
      1) machine naming. Either you're connecting to a machine with a different DNS than its actual machine name. Or, you created a service reference to one machine name, and the you're now connecting to a different name. If you try the IP address instead of the DNS name, it may or may not go away.
      2) if you look at the .config file for your .NET code, you'll find some information related to the service connection. I've seen it sometimes storing the AOS' service account name in the principal information. Try blanking those things out.

      Delete
  16. Hi Joris,

    How to integrate Microsoft dynamic Ax with Dot Net through AIF.
    Please help us with the suitable article.

    ReplyDelete
    Replies
    1. I assume that means you are well versed in .NET... All you need to do is make a service reference in your .NET project, it's that easy. If you're not sure what that means you should probably contract someone that knows integrations.
      I have examples on this blog on integrating, like the WPF app and windows phone examples you can find under the 2012 resources page. There's also plenty of official documentation on MSDN which you can find with an easy web search. The Microsoft download site also has documentation on this subject. https://msdn.microsoft.com/en-us/library/aa877498.aspx

      A lot of time just using Bing or Google is the best way to get an answer!

      Delete