I think the Dynamics CRM 2011 SDK is swell for interoperability, but I wanted to get a closer look at how the actual web service calls work, so I decided to access the sandbox CRM instance my company provides using a WSDL-based proxy as described here. Because the SDK has several examples for connecting to CRM instances using different kinds of authentication in the SDK\SampleCode\CS\WsdlBasedProxies directory, I figured this would be a piece of cake. As it turns out, I was wrong.
Disclaimer: before asking anyone read through to the end of this post, let me say I think the problem I ran into may be fairly specific to my organization's ADFS setup because despite a lot of web searches, I couldn't find anything that specifically addressed my issue from a Dynamics CRM perspective. On the other hand, if you are having the same problem I did, maybe this post will save you some time.
Because my CRM sandbox instance is an Internet-facing (IFD) deployment, I looked at the ifd project in the SDK's wsdlbasedproxies solution and followed the setup instructions. I ran into a problem almost immediately. After creating service references for the Discovery and Organization services, the next instruction is:
In the "
" node, locate the " " node. This node allows the user to specify which Issuer should be used when issuing a token. The various STS endpoints will be present in the commented out "alternativeIssuedTokenParameters" node. In my example, UserNameMixed is used. In order to use UserNameMixed, the "binding" and "bindingConfiguration" attributes need to be updated to match the node in "alternativeIssuedTokenParameters". The easiest way to find this endpoint is to search for "https://adfs.contoso.com:555/adfs/services/trust/13/usernamemixed". In the generated configuration file, you'll find: Copy this node and replace the current issuer node in the " " node.
Well, in my app.config file, there was no alternativeIssuedTokenParameters node. All I had was these two elements in my CustomBindingIOrganizationService and CustomBindingIDiscoveryService bindings:
<issuer address="http://schemas.microsoft.com/2005/12/ServiceModel/Addressing/Anonymous" /> <issuerMetadata address="https://ADFS.SOMEDOMAIN.COM/adfs/services/trust/mex" />
I figured what the heck, let's try to run the sample in debug mode anyway and see if it works. Spoiler alert: it didn't. Instead of connecting to CRM, I got a Windows CardSpace (never heard of it before) screen pop up asking me if I wanted to send a card to the site. Seeing as the SDK readme doc had made of mention of CardSpace, I figured something must be misconfigured, so I closed the window and stopped debugging.
It took me a while to figure it out (I'm a developer, not a networking guy), but I finally realized that my WCF client knew it should be using a token from the SOMEDOMAIN.COM STS to talk to CRM, but it didn't know how to get one. After some fruitless searching, I stumbled upon a post by the MCS UK dev team called "Federated Security: How to setup and call a WCF service secured by ADFS 2.0." At first I looked at programmatically getting a token, but then I decided it would be much simpler to take the config-based approach described in this MSDN tutorial: http://msdn.microsoft.com/en-us/gg557876. Also, the config-based approach is what should have been happening in the SDK sample anyway.
To get the issuer element of my bindings right, I first created a service reference for the address in the issuerMetadata element. As soon as I did, my app.config file was updated with lots of binding and endpoints that looked like what was described in the SDK sample setup file, so I figured I must be on the right track.
Knowing that my sandbox CRM instance used the "/adfs/services/trust/13/username" endpoint for getting a token from the ADFS STS (thanks to watching Fiddler while a desktop application that uses the SDK connected to my CRM instance), I searched in the updated app.config file for that string, and I found this element:
<endpoint address="http://ADFS.SOMEDOMAIN.COM/adfs/services/trust/13/username" binding="ws2007HttpBinding" bindingConfiguration="UserNameWSTrustBinding_IWSTrust13Async" contract="STS.IWSTrust13Async" name="UserNameWSTrustBinding_IWSTrust13Async"> <identity> <certificate encodedValue="REALLY_LONG_CERTIFICATE_STRING..." /> </identity> </endpoint>
Next, I commented out the existing issuer and issuerMetadata elements in my CRM service bindings and replaced them with the address, binding, binding configuration and identity details from the STS endpoint like so:
<issuer address="http://ADFS.SOMEDOMAIN.COM/adfs/services/trust/13/username" binding="ws2007HttpBinding" bindingConfiguration="UserNameWSTrustBinding_IWSTrust13Async" > <identity> <certificate encodedValue="REALLY_LONG_CERTIFICATE_STRING..." /> </identity> </issuer>
After that, I ran the sample project in debug mode, and it worked perfectly.
Why did this happen?
I will readily admit I don't understand the ins and outs of ADFS. I get the point of it and the basics of how it works, but I certainly don't know why the configuration of my CRM instance would lead to these results. All I can say at this point is that I'm glad I've got it working, and I'll know what to do in the future if a problem like this ever comes up again.