Walkthrough: Server to Server Authentication with CRM Online
This walkthrough shows how to access Microsoft Dynamics CRM Online Web services by using Windows Live authentication. This scenario is used for accessing the Microsoft Dynamics CRM Web services from a Windows service, Web service, or ASPX page.
During this walkthrough you will learn how to do the following:
- Use Visual Studio 2008 to create a console application that uses the Microsoft Dynamics CRM Online Web services.
- Use Windows Live authentication to access the Microsoft Dynamics CRM Online Web services.
This walkthrough uses C# sample code only. However, a Visual Basic .NET version of the code can be found at SDK\Walkthroughs\Authentication\VB\ServerToServer.
Note In order to use this new authentication model you must first obtain a certificate and then follow the documented procedure to download the Sign-in Assistant software from the Windows Live Web site and associate the certificate to your Windows Live ID. The documentation is located at SDK\Walkthroughs\Authentication\CS\ServerToServer\Cert\Cert to WLID Association.doc.
Prerequisites
In order to complete this walkthrough, you will need the following:
- SSL certificate
- Visual Studio 2008
- Microsoft Dynamics CRM Online user account that has security privileges to add a new account to your organization
- Access to the SDK download (version 4.0.8 or higher)
You must follow and complete the instructions that are provided in the "Cert to WLID Association.doc" document. This document describes how to associate a Windows Live ID with an installed certificate. The document can be found in the SDK\Walkthroughs\Authentication\CS\ServerToServer folder.
Creating a Visual Studio 2008 Solution
Use Visual Studio to create a solution to build your code.
To create a Visual Studio solution
- In Microsoft Visual Studio, on the File menu, point to New, and then click Project to open the New Project dialog box.
- In the Project types pane, select Visual C#.
- In the Templates pane, click Console Application.
- Type a name for your project and then click OK.
Downloading the Web Service Description Files
For programming against Microsoft Dynamics CRM Online, you have to obtain local copies of the Web Service Description Language (WSDL) files for your organization. Be aware that for the WSDL to be complete, you must repeat the following procedure if you make any customizations to entities in your organization.
- Open a Web browser and enter the URL for the Microsoft Dynamics CRM Online portal. For example:
- Click the Log In Here button, enter your Windows Live account logon information, and then click Sign in.
- In the Navigation pane click Settings.
- In the Settings pane click Customization.
- In the Customization area, click Download Web Service Description Files.
- Click the CrmService.asmx icon.
- When the Web browser displays the WSDL, save the XML to a file on your hard disk. The procedure for saving the file will be different depending on the Web browser that you are using. In Internet Explorer 7, click the Page menu, and then click Save.
- Click the CrmDiscoveryService.asmx icon.
- Download the WSDL as you did before in step 7. If you see a CrmDiscoveryService Web page instead of WSDL, click the Service Description link to obtain the WSDL.
- Close the Web browser window that contains the WSDL definitions and sign out of Microsoft Dynamics CRM Online.
Adding References
You have to add references to the required Microsoft Dynamics CRM Online Web services; in this case, you will add a reference to the CrmDiscoveryService Web service and the CrmService Web service. You also have to add two references to .NET components.
To add the CrmService Web service reference
- In the Solution Explorer window, right-click your project name and select Add Service Reference.
- In the Add Service Reference dialog box, click Advanced.
- In the Service Reference Settings dialog box, click Add Web Reference.
- Type the URL for the CrmService WSDL XML file that you previously downloaded into the URL box, and then click Go. For example:
file://C:\Documents and Settings\user\My Documents\CrmServiceWsdl.xml
- When the CrmService Web service is found, change the text in the Web reference name box to CrmSdk and then click Add Reference.
To add the CrmDiscoveryService Web service reference
- In the Solution Explorer window, right-click your project name and select Add Service Reference.
- In the Add Service Reference dialog box, click Advanced.
- In the Service Reference Settings dialog box, click Add Web Reference.
- Type the URL for the CrmDiscoveryService WSDL XML file that you previously downloaded into the URL box, and then click Go. For example:
file://C:\Documents and Settings\user\My Documents\CrmDiscoveryServiceWsdl.xml
- When the CrmDiscoveryService Web service is found, change the text in the Web reference name box to CrmSdk.Discovery and then click Add Reference.
To add the required .NET references
- In the Solution Explorer window, right-click your project name and select Add Reference.
- In the Add Reference dialog multi-select the System.IdentityModel and System.ServiceModel components, and then click OK.
Adding a Windows Live Helper Class
You now have to add helper class code that simplifies Windows Live authentication to your project.
To add the helper code to your project
- Right-click the project name in Solution Explorer.
- Select Add then select Existing Item.
- In the file dialog box, browse to the folder SDK\server\helpers\cs\CrmOnlineAuth.
- Select the WindowsLiveIdTicketAcquirer.cs file and then click Add.
Accessing Microsoft Dynamics CRM Online Web Services
You will first add using statements to provide access to the required namespaces, and a class definition to your project.
To add the required using statements
In the default Program.cs file that Visual Studio provides, replace all code in that file with the following C# code:
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Web.Services.Protocols;
using System.Security.Cryptography.X509Certificates;
namespace Microsoft.Crm.Sdk.Walkthrough.Authentication.PartnerAuthConsoleApp
{
// Import the Microsoft Dynamics CRM namespaces.
using CrmSdk;
using CrmSdk.Discovery;
class PartnerAuthentication
To add configuration information to your solution
Add the following code within the PartnerAuthentication class definition. Make sure that you fill in the correct certificate name, WLID service account, and organization values for your installation.
[C#]
// Cert name installed on ISV's server.
// The ISV is responsible for obtaining their own cert.
static private string _certSubjectCN = "CertName";
// Set the name ( and if TCP port) of the server hosting Microsoft Dynamics CRM Online.
static private string _hostname = "dev.crm.dynamics.com";
// This is the WLID account for the ISV's service.
static private string _serviceAcctWLID = "ISVServiceAccount@hotmail.com";
// This is open information about CRM Online.
static private string _passportDomain = "crm.dynamics.com";
// Set the friendly name of the target organization.
static private string _orgFriendlyName = "AdventureWorksCycle";
// Set the type of Environment (i.e. INT/PPE/PROD).
static private WindowsLiveIdTicketAcquirer.Environment _environment =
WindowsLiveIdTicketAcquirer.Environment.PROD;
// Defines the expired authentication ticket error code.
static private string EXPIRED_AUTH_TICKET = "8004A101";
// Attempt a service call a maximum number of times before failing.
static private int MAX_RETRIES = 5;
To add a Main method with error handling to your solution
Add the following code after the AuthenticationType class. This code will provide some exception handling and console output to verify that your program is working.
[C#]
static void Main(string[] args)
{
try
{
Run();
Console.WriteLine("Authentication was successfull.");
}
catch (System.Exception ex)
{
Console.WriteLine("The application exited with an error.");
Console.WriteLine(ex.Message);
// Display the details of the inner exception.
if (ex.InnerException != null)
{
Console.WriteLine(ex.InnerException.Message);
SoapException se = ex.InnerException as SoapException;
if (se != null)
Console.WriteLine(se.Detail.InnerText);
}
}
finally
{
Console.WriteLine("Press <Enter> to exit.");
Console.ReadLine();
}
}
Your program will not build because the Run method does not exist. You will create that method next.
To add the Run and InvokeServiceMethod methods
The InvokeServiceMethod contains all the code needed to use the discovery service to obtain the correct URL for the CrmService of your organization. A WhoAmI request is sent to the Web service to verify that the user has been authenticated successfully.
Add the following class methods after the Main method.
public static bool Run()
{
try
{
// Connect to Microsoft Dynamics CRM and call a Web service method.
int firstAttempt = 1;
InvokeServiceMethod(firstAttempt);
}
// Handle any Web service exceptions that might be thrown.
catch (SoapException ex)
{
// Handle the SOAP exceptions here.
throw ex;
}
catch (Exception ex)
{
// Handle general exceptions here.
// This sample will just rethrow the exception.
throw ex;
}
return true;
}
public static void InvokeServiceMethod(int retryCount)
{
try
{
if (retryCount == MAX_RETRIES)
{
// Display an error when the maximum retry count has been reached.
throw new Exception("An error occurred while attempting to authenticate.");
}
else
{
// STEP 1,2: Retrieve a policy from the Discovery Web service.
CrmDiscoveryService discoveryService = new CrmDiscoveryService();
discoveryService.Url = String.Format(
"https://{0}/MSCRMServices/2007/{1}/CrmDiscoveryService.asmx",
_hostname, "Passport");
RetrievePolicyRequest policyRequest = new RetrievePolicyRequest();
RetrievePolicyResponse policyResponse =
(RetrievePolicyResponse)discoveryService.Execute(policyRequest);
// STEP 3,4: Retrieve a ticket from the Windows Live service.
string wlidTicket = null;
wlidTicket = WindowsLiveIdTicketAcquirer.RetrieveTicket(RetrieveCertificate(
_certSubjectCN), _serviceAcctWLID, _environment, _passportDomain,
policyResponse.Policy);
// STEP 5,6: Retrieve organization information and a (Crm) ticket from the Discovery
// Web service. Retrieve a list of organizations that the logged on user is a member of.
RetrieveOrganizationsRequest orgRequest = new RetrieveOrganizationsRequest();
orgRequest.PassportTicket = wlidTicket;
RetrieveOrganizationsResponse orgResponse =
(RetrieveOrganizationsResponse)discoveryService.Execute(orgRequest);
// Locate the target organization name using the organization friendly name.
String orgUniqueName = String.Empty;
OrganizationDetail orgInfo = null;
foreach (OrganizationDetail orgDetail in orgResponse.OrganizationDetails)
{
if (orgDetail.FriendlyName.Equals(_orgFriendlyName))
{
orgInfo = orgDetail;
orgUniqueName = orgInfo.OrganizationName;
break;
}
}
// Retrieve the CrmTicket.
RetrieveCrmTicketRequest crmTicketRequest = new RetrieveCrmTicketRequest();
crmTicketRequest.OrganizationName = orgUniqueName;
crmTicketRequest.PassportTicket = wlidTicket;
RetrieveCrmTicketResponse crmTicketResponse =
(RetrieveCrmTicketResponse)discoveryService.Execute(crmTicketRequest);
// STEP 7: Create and configure an instance of the CrmService Web service.
CrmAuthenticationToken token = new CrmAuthenticationToken();
token.AuthenticationType = AuthenticationType.Passport;
token.CrmTicket = crmTicketResponse.CrmTicket;
token.OrganizationName = crmTicketResponse.OrganizationDetail.OrganizationName;
CrmService crmService = new CrmService();
crmService.Url = crmTicketResponse.OrganizationDetail.CrmServiceUrl;
crmService.CrmAuthenticationTokenValue = token;
// Invoke the desired CrmService Web service methods.
WhoAmIRequest whoRequest = new WhoAmIRequest();
WhoAmIResponse whoResponse = (WhoAmIResponse)crmService.Execute(whoRequest);
systemuser user = (systemuser)crmService.Retrieve(EntityName.systemuser.ToString(),
whoResponse.UserId, new AllColumns());
Console.WriteLine("Login ISV user's name is {0}", user.fullname);
}
}
catch (SoapException ex)
{
// Handle the exception thrown from an expired ticket condition.
if (GetErrorCode(ex.Detail) == EXPIRED_AUTH_TICKET)
{
// This will retry the CrmService Web service call.
InvokeServiceMethod(retryCount++);
}
// If this was some other SOAP exception, then rethrow this exception.
throw ex;
}
catch (Exception ex)
{
// Handle the MAX_RETRY exception here.
// This sample will rethrow the exception.
throw ex;
}
}
To add the Certificate methods
Add the following class methods after the InvokeServiceMethod method. The certificate methods retrieve a named certificate from the certificate store and validate a certificate.
protected static X509Certificate2 RetrieveCertificate(string subjectCN)
{
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
// Microsoft recommends to keep third parameter of Find method to true for
// retrieving valid certs.
X509Certificate2Collection certs = store.Certificates.Find(
X509FindType.FindBySubjectName, subjectCN, true);
if (null != store)
{
store.Close();
}
if (null == certs || certs.Count < 1)
{
throw new Exception("Cannot find specified certificate.");
}
ValidateCert(certs[0]);
// Return the first match.
return certs[0];
}
static void ValidateCert(X509Certificate2 cert)
{
X509Chain chain = new X509Chain();
// Check entire chain for revocation.
chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
// Check online or offline revocation lists.
chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
// Set timeout for online revocation list.
chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 30);
// No exceptions, check all properties for checking all flags.
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
// Set or modify time of verification.
//chain.ChainPolicy.VerificationTime = new DateTime(1999, 1, 1);
chain.Build(cert);
if (chain.ChainStatus.Length != 0)
{
// Throw exception here for invalid cert.
}
To add the GetErrorCode method
Add the following class method after the ValidateCert method. This method returns the error code from a SOAP exception stored in an XmlNode or returns an empty string if no error exists.
private static string GetErrorCode(XmlNode errorInfo)
{
XmlNode code = errorInfo.SelectSingleNode("//code");
if (code != null)
return code.InnerText;
else
return "";
Setting Project Properties and Building
In the Project menu, select the Properties command. In the Application property tab enter the application namespace, Microsoft.Crm.Sdk.Walkthrough.Authentication.PartnerAuthConsoleApp, in the Default namespace text box. Now build and run your solution by pressing F5. When the program runs, it will print "Login user's name is …" to the console window.
If you have compile errors, you can find the complete code sample in the SDK\Walkthroughs\Authentication\CS\ServerToServer folder.
© 2009 Microsoft Corporation. All rights reserved.