Friday, June 27, 2008

CRM 4 Web Service 401: Unauthorized error

Hi all,

Been upgrading some of our CRM 3.0 components to CRM 4.0 version, just to find out a lot was changed in the web services access.

For one, now that CRM supports hosted mode, more than one organization could be configured so now you have to set up the organization you are working on with your web services serquests.

This did not took me long to find out - just call the CrmDiscoveryService, create a new RetrieveOrganizationsRequest request, and execute it.
Code:
crmDisco.CrmDiscoveryService ds = new KWizCom.CRM.Utility.crmDisco.CrmDiscoveryService();
ds.Url = CrmWebServicesUrl + "AD/CrmDiscoveryService.asmx";
ds.PreAuthenticate = true;
ds.UseDefaultCredentials = true;
//create request
crmDisco.RetrieveOrganizationsRequest r = new KWizCom.CRM.Utility.crmDisco.RetrieveOrganizationsRequest();
crmDisco.RetrieveOrganizationsResponse rr = (crmDisco.RetrieveOrganizationsResponse)ds.Execute(r);
SetOrganization(rr.OrganizationDetails[0]);


And now that you got your organization (first one in the array), you need to use it when calling all other web service (example: creating CrmService and MetadataService
Code:
crmMeta.CrmAuthenticationToken token = new crmMeta.CrmAuthenticationToken();
token.OrganizationName = this.Organization.OrganizationName;
token.AuthenticationType = Enums.AuthenticationType.AD;

this.metadataService = new crmMeta.MetadataService();
this.metadataService.Url = this.Organization.CrmMetadataServiceUrl;
this.metadataService.CrmAuthenticationTokenValue = token;
this.metadataService.PreAuthenticate = true;
this.metadataService.UseDefaultCredentials = true;

crm.CrmAuthenticationToken token2 = new crm.CrmAuthenticationToken();
token2.OrganizationName = this.Organization.OrganizationName;
token2.AuthenticationType = Enums.AuthenticationType.AD;

this.crmService = new crm.CrmService();
this.crmService.Url = this.Organization.CrmServiceUrl;
this.crmService.CrmAuthenticationTokenValue = token2;
this.crmService.PreAuthenticate = true;
this.crmService.UseDefaultCredentials = true;


Now you are all set for using the web services Under The Current User Account!!!.

What if, like me, you need to call the web service using a user name and password?

The logical to do is impersonation in web services 101:
Create a NetworkCredential Object, and set it to your crmService.Credentials,
like so:
ICredentials WebServiceCredentials = new System.Net.NetworkCredential(userName,password,domain);
this.crmService.UseDefaultCredentials = false;
this.crmService.Credentials = WebServiceCredentials;


Right? Wrong!

CRM web service will return 401 error (unauthorized access) every time you do it.

The solution for this was found in a great msdn post:
http://msdn.microsoft.com/en-us/library/cc151049.aspx

The I found that the web service credentials must not be changed at all.
What needs to be done it to make the request under a user with permissions to the CRM (like administrator or something), and pass the user name and password in the request itself.

For getting the organization, you will need to modify your code like this:
crmDisco.CrmDiscoveryService ds = new KWizCom.CRM.Utility.crmDisco.CrmDiscoveryService();
ds.Url = CrmWebServicesUrl + "AD/CrmDiscoveryService.asmx";
ds.PreAuthenticate = true;
ds.UseDefaultCredentials = true;
//create request
crmDisco.RetrieveOrganizationsRequest r = new KWizCom.CRM.Utility.crmDisco.RetrieveOrganizationsRequest();
if (!string.IsNullOrEmpty(UserName) && !string.IsNullOrEmpty(Password))
{
r.UserId = this.Domain + "\\" + this.UserName;
r.Password = this.Password;
}

crmDisco.RetrieveOrganizationsResponse rr = (crmDisco.RetrieveOrganizationsResponse)ds.Execute(r);

SetOrganization(rr.OrganizationDetails[0]);


And also - you will have to get a ticket for the other web services authentication:
RetrieveCrmTicketRequest ticketRequest = new RetrieveCrmTicketRequest();
ticketRequest.OrganizationName = Organization.OrganizationName;
ticketRequest.UserId = this.Domain + "\\" + this.UserName;
ticketRequest.Password = this.Password;
ticketResponse = (RetrieveCrmTicketResponse)ds.Execute(ticketRequest);


Using the response later when you configure your other web services like so:

crmMeta.CrmAuthenticationToken token = new crmMeta.CrmAuthenticationToken();
token.OrganizationName = this.Organization.OrganizationName;
token.AuthenticationType = Enums.AuthenticationType.AD;
if( ticketResponse != null )
token.CrmTicket = ticketResponse.CrmTicket;

this.metadataService = new crmMeta.MetadataService();
this.metadataService.Url = this.Organization.CrmMetadataServiceUrl;
this.metadataService.CrmAuthenticationTokenValue = token;
this.metadataService.PreAuthenticate = true;
this.metadataService.UseDefaultCredentials = true;

crm.CrmAuthenticationToken token2 = new crm.CrmAuthenticationToken();
token2.OrganizationName = this.Organization.OrganizationName;
token2.AuthenticationType = Enums.AuthenticationType.AD;
if (ticketResponse != null)
token2.CrmTicket = ticketResponse.CrmTicket;

this.crmService = new crm.CrmService();
this.crmService.Url = this.Organization.CrmServiceUrl;
this.crmService.CrmAuthenticationTokenValue = token2;
this.crmService.PreAuthenticate = true;
this.crmService.UseDefaultCredentials = true;


Now, you should be able to call the web services with impersonation to other CRM accounts...

Hope this helps you, I sure got stuck on this for a while.

Thanks, Shai.

4 comments:

Matt said...

Hi there,
Thank you for your post. It helped me in my own environment. now trying to access CRM Webservices in the LIVE environment (windows 2008 IIS 7 etc) and getting Unauthorised 401 error. It fails on this call:

RetrieveOrganizationsResponse rr = (RetrieveOrganizationsResponse)ds.Execute(r);

looking at the HTTP logs on the LIVE server, it shows that it is not receiving the domain\Username:

/mscrmservices/2007/AD/CrmDiscoveryService.asmx - 80 - 192.168.120.68

whereas if I access the crm from a browser it returns:

/MSCrmServices/2007/CrmServiceWsdl.aspx - 80 MYDOMAIN\myuseraccount 192.168.120.68

any ideas?

Kevin said...

ds.Credentials = new NetworkCredential(user, passwd, domain);

The above worked for me without using the CrmTicket and without passing user/pass to the org. request.

Rami Heleg said...

Hi,
i try to run web services from plugin. not crm web service my own custom web services and get error 401.
i don't want to use username,password and domain hard coded just to get the Credentials for the current user.
how can i do it..?
thanks
Rami Heleg.
rheleg@hotmail.com

Shai Petel said...

Hi Rami,

Not sure I understand, if you enable impersonation you should be able to get the current user's credentials in your web service.
Not the password though, but you don't need the password.

Let me know if I am missing something.