Issue Contacting ADFS Endpoint When on Server - wcf

I have a service that is setup to retrieve a secure token from ADFS and use that token to communicate with other services. When I contact my ADFS windowsmixed endpoint from my local development machine hitting the ADFS service I am able to successfuly retrieve the token. However, when I install my service on the same machine that is running ADFS I receive the following error:
Secure channel cannot be opened because security negotiation with the remote endpoint has failed. This may be due to absent or incorrectly specified EndpointIdentity in the EndpointAddress used to create the channel. Please verify the EndpointIdentity specified or implied by the EndpointAddress correctly identifies the remote endpoint.
I am able to reproduce the error with the following code that simply gets the token. Again this code works when I am on my dev machine hitting the remote server, but it fails when on the server directly. I am using the same user credentials on both. I get the same error within the IIS web service using the app pool credentials and with a simple test client using the code below.
private static SecurityToken GetToken()
{
string stsEndpoint = "https://adfsserver.com/adfs/services/trust/13/windowsmixed";
string appliesTo = "http://domain.com/application/myapplication";
var factory = new WSTrustChannelFactory(
new WindowsWSTrustBinding(SecurityMode.TransportWithMessageCredential),
stsEndpoint);
factory.TrustVersion = TrustVersion.WSTrust13;
var rst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
AppliesTo = new EndpointAddress(appliesTo),
KeyType = KeyTypes.Symmetric
};
var channel = factory.CreateChannel();
return channel.Issue(rst);
}
I turned on tracing in the Windows Event Log for ADFS 2.0 debug. When hitting that windowsmixed endpoint directly on the server, I do not receive any entries which leads me to belive that it is not actually getting to the endpoint.
I do receive quite a few audit failures in the security log that are related to the services that I am running:
A handle to an object was requested.
Subject:
Security ID: DOMAIN\ODI$ODIController
Account Name: ODI$ODIController
Account Domain: DOMAIN
Logon ID: 0x1a574b5
Object:
Object Server: SC Manager
Object Type: SERVICE OBJECT
Object Name: WinHttpAutoProxySvc
Handle ID: 0x0
Process Information:
Process ID: 0x1f8
Process Name: C:\Windows\System32\services.exe
Access Request Information:
Transaction ID: {00000000-0000-0000-0000-000000000000}
Accesses: Query status of service
Start the service
Query information from service
Access Reasons: -
Access Mask: 0x94
Privileges Used for Access Check: -
I am able to access the usernamemixed endpoint using stored credentials and receive the proper token, so it seems to be something with authenticating the user to even be able to communicate with the ADFS endpoint.
If I set specific credentials in the code above, it is able to connect. Which again leads me to believe that it is not passing the correct credentials for my Windows user when on the same machine.
factory.Credentials.Windows.ClientCredential = new System.Net.NetworkCredential("UserID", "password1", "dev.domain");
Thank you for any assistance you can provide.
Brian

I had a similar issue. I was able to get it working using the example from here: http://blogs.southworks.net/mwoloski/2009/07/17/getting-a-token-from-adfs-ex-geneva-server-using-wcf/
The difference between your code and the working example is that you modify the message security to use the current security credentials in the binding rather than on the client. If you are using WIF 4.0, you need to modify the code to use a WSTrustChannelFactory instead of WSTrustClient. The other code doesn't change much though.
My code for the factory looks like this:
var binding = new WS2007HttpBinding(SecurityMode.TransportWithMessageCredential);
binding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;
binding.Security.Message.EstablishSecurityContext = false;
var factory = new WSTrustChannelFactory(
binding,
new EndpointAddress(new Uri(sts), EndpointIdentity.CreateUpnIdentity(adfsUpn)));

Related

ASP.NET Core WEB API self-connection through external URL "No connection could be made because the target machine actively refused it"

I have multitenant Web API application with Hangfire scheduler that calls a task which trying to connect to self endpoint through external server DNS name with tenant name in it. Doing it this way, because Hangfire doesn't have HttpContext to resolve on which tenant task must shoot. On my staging server or if I send this request from Postman it works nice, but on my local machine and on production server it raises the error from title.
The code looks like nothing special:
var url = $"{tenant.Url}/notifications/client/send-appointment-sms";
var jwtToken = await _authService.GetTokenByUsername("admin");
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwtToken.AccessToken);
var response = await _httpClient.SendAsync(requestMessage);
Where tenant.Url is for example https://api.tenant1.example.com/api2 which is available from browser on production server where application is running.
If I understanding clearly, my application can not connect to itself for some reason.
I have tried common solutions like firewall settings or disable proxy, still nothing on this point. What can I check next?
UPD I must add that NodeJS server on production is able to call this service too, so I think issue is not in server but maybe more about ASP.NET configuration settings.

Remote service call through proxy

I'm trying to make a very simple service call from VS2012.
The service is on a domain outside a proxy and requires logon credentials.
I have set a refrence to the service in visuals studio. At that point i entered in the remote domian username and password and VS created all the proxy classes for me.
I then added this line to appconf file.
<system.net>
<defaultProxy enabled="true" useDefaultCredentials="true">
</defaultProxy>
</system.net>
Which i believe will allow me to get through our proxy using my own credentails
I then wronte this simple piece of code
private void GetData()
{
OASIS.OasisServiceSoapClient o = new OASIS.OasisServiceSoapClient();
o.ClientCredentials.UserName.UserName = #"OtherDimain\UserName";
o.ClientCredentials.UserName.Password = "Password";
var d = o.SelectOfficersAll();
}
and of course it didn't work and i got all the errors that everyone has posted on.
So first question is
do i need to add this
o.ClientCredentials.Windows.ClientCredential = System.Net.CredentialCache.DefaultNetworkCredentials;
because i did and still get that same stupid error
"The HTTP request is unauthorized with client authentication scheme 'Anonymous'. The authentication header received from the server was 'Negotiate,NTLM'."
and inner exception
"{"The remote server returned an error: (401) Unauthorized."}"
so am i getting through the proxy ?
Am i using my own credentials ?
Am i passing the right paramaters in to the Service Model ?
Some examples show the username and password properties in the code above are to impersonate the current job.
But i read these on the MSDN page as being the credentials you want to use on the remote serve. The Help topic is ambigious. And if i don't enter them here then how ?
I'm trying to do something so simple , yet can't seem to get past this point.
Ok thanks to my Colleague Sean. It seems that depending on wether you are calling a web service or a WCF services determines what you need to do.
So as a web service this works
OASISWeb.OasisService s = new OASISWeb.OasisService();
s.Credentials = new System.Net.NetworkCredential("Username", "Password", "Domain");
var d = s.SelectOfficersAll();
DataSet x = (DataSet)d;
if it's a WCF service then you need this
var service = new OasisTest2.ServiceReference1.OasisServiceSoapClient();
System.Net.WebRequest.DefaultWebProxy.Credentials = system.Net.CredentialCache.DefaultNetworkCredentials;
service.ClientCredentials.Windows.ClientCredential = new System.Net.NetworkCredential("Username", "Password", "Domain");
var result = service.SelectOfficersAll();
It seems that WebRequest is a global object and you need to set the DefaultWebProxy.Credentails on it.
How you are suppose to know that ? I never found any reference to it when i searched on how to connect to a WCF service on MSDN. Must be a secret. So keep it under your hat.

Client Authentication when using Service Bus for Windows Server

I'm currently working on a project where we use the Service Bus for Windows Server queues and topics for handling messaging between clients and server. Currently I'm looking into how to handle authentication of the clients and believe we need to use SAS. The clients communicating with the queues can be using both rest and the .net api. I have tried to find resources on best practices especially on how to handle token generation and distribution. For example should we create a service for this that the calling client can connect to providing the access key which would then generate a token returned to the client. Ideas and suggestions would be greatly appreciated.
/Thanks
I would recommend using the WindowsTokenProvider and a Windows user account.
First add a valid Windows user to the Bus with Send permissions on your queue/topic. (You can do this in code or via service bus explorer.)
Then for the Rest API client authentication over Http - You need to post over a valid UserName/Pwd to the STS then add the resulting token to the authorization header of the actual post to the queue.
You can see how to do this here...
For the .Net client over TCP -
Run your client as the same valid windows user, and then call the STS with these implicit credentials by using the TokenProvider.CreateWindowsTokenProvider.
3.1 If you are using the NetMessagingBinding for your client (WCF) then do the following:
var uri = new Uri(string.Format("https://{0}:9355/ServiceBusDefaultNamespace", serverName));
var uris = new List<Uri> {uri};
var securityBehavior = new TransportClientEndpointBehavior
{
TokenProvider = TokenProvider.CreateWindowsTokenProvider(uris)
};
var endpoint = new ServiceEndpoint(contract, new NetMessagingBinding(), new EndpointAddress(serviceBusEndpointAddress));
endpoint.Behaviors.Add(securityBehavior);
3.2 NB - The code above will take the implicit credentials of the current principal and pass them across. However, you can explicitly pass in credentials like this:
var securityBehavior = new TransportClientEndpointBehavior
{
TokenProvider = TokenProvider.CreateWindowsTokenProvider(uris, new NetworkCredential("myUser", "myPassword"))
};
3.3 Or if you just use the plain .NetClient you can do the same like this:
var uri = new Uri(string.Format("https://{0}:9355/ServiceBusDefaultNamespace", serverName));
var uris = new List<Uri> {defaultUri};
var messagingFactorySettings = new MessagingFactorySettings();
messagingFactorySettings.TokenProvider =
TokenProvider.CreateWindowsTokenProvider(uris, new
NetworkCredential("myUser", "myPassword"));
.....
var factory = MessagingFactory.Create("endpoint", messagingFactorySettings);

Dynamics CRM - Caller not authenticated to service

I have an MVC4 Web Application on Web Server A that is consuming the Dynamics CRM Web Service using the OrganizationServiceProxy, which is on Web Server B. The MVC4 application is setup with ASP .NET Impersonation and Windows Authentication enabled. When I call the WhoAmI I get an error:
'The caller was not authenticated by the service.'
Now if I move the MVC4 Application to Web Server B (same as CRM) with the same Authentication as it had on Web Server A it calls WhoAmI without an exception.
Here is the code being used to connect to the server.
string serviceURL = ConfigurationManager.AppSettings["CRMROOTURL"].ToString() + "XRMServices/2011/Organization.svc";
this.CRMService = GetCRMService(serviceURL);
private OrganizationServiceProxy GetCRMService(string serviceURL)
{
ClientCredentials credentials = new ClientCredentials();
credentials.Windows.ClientCredential = CredentialCache.DefaultNetworkCredentials;
OrganizationServiceProxy client
= new OrganizationServiceProxy(new Uri(serviceURL), null, credentials, null);
return client;
}
Here is a screenshot of the authentication on the IIS Web Site.
Per the correct answer I just wanted to provide some snippets to help anyone else.
string loggedUser = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
ClientCredentials credentials = new ClientCredentials();
credentials.Windows.ClientCredential = new NetworkCredential(username, password, domain);
OrganizationServiceProxy client
= new OrganizationServiceProxy(new Uri(serviceURL), null, credentials, null);
client.ClientCredentials.Windows.ClientCredential = credentials.Windows.ClientCredential;
// -- Retrieve the user.
QueryExpression expression = new QueryExpression
{
EntityName = "systemuser",
ColumnSet = new ColumnSet("systemuserid")
};
expression.Criteria.AddCondition("domainname", ConditionOperator.Equal, loggedUser);
EntityCollection ec = client.RetrieveMultiple(expression);
if (ec.Entities.Count > 0)
{
// -- Impersonate the logged in user.
client.CallerId = ec.Entities[0].Id;
}
Thanks!
Unless you explicitly state otherwise (and without any code to see how you are creating your OrganizationServiceProxy), on premise OrganizationServiceProxies will use the current AD account (of the service account, not the user's specific account) to connect to CRM. I'm guessing that the App pool you're running on Server A isn't a CRM user, and the one on Server B is. If so, either change Server A's user to be the same user as Server B, or make the Server A's user a user in CRM.
Edit
You're using the default network credentials to connect to CRM. This mean that no matter what IIS authentication you are using, you will connect to CRM as the App Pool User Account. This works as long as the App Pool user is a CRM user, but is probably not what you want.
You can set the network credential manually using this method:
creds.Windows.ClientCredential = new System.Net.NetworkCredential("UserId", "Password", "DomainName");
Then get the ASP.Net User's domain name and use impersonation to connect to CRM to ensure that all of the security for that individual is correctly applied.
Something stupid - be careful you aren't escaping your user name!
creds.Windows.ClientCredential = new NetworkCredential("domain\user", "PASSWORD");
Notice that the \u is an escape sequence - you need to type "domain\user".

Reflection, WCF Web service, LoadFrom Assembly oh my. Issues with Network Credentials

I've created a WCF webservice, that can dynamically call other webservices/db connections with DLL's loaded with .LoadFile('from assembly'). Inside one of these assemblies, another webservice is called dynamically with a passed in network credential as follows:
WebClient client = new WebClient();
client.Credentials = this.networkCredential; //This credential is passed in
RequestStream requestStream = client.OpenRead(this.url);
//rest of code. The .OpenRead is giving 401 error (not authorized).
When I do this in debug mode from a test console application and creating the network credentials as follows:
NetworkCredential networkCredential = new NetworkCredential(<userid>,<password>,<domain>);
this works fine.
The failing code is providing networkcredentials as follows
System.Net.NetworkCredential networkCredential = System.Net.CredentialCache.DefaultNetworkCredentials;
Any help would be greatly appreciated.
Unless you are using impersonation, DefaultNetworkCredentials attempts to use the credentials of the process running your ASP.NET website, not the credentials of the user accessing your site. In order to use the credentials of the user, you should wrap your call to the web services in an impersonation context.
var impersonationContext = HttpContext.Current.Request.User.Identity.Impersonate();
if(impersonationContext != null)
{
//call your webservice here. DefaultNetworkCredentials will be what you expect.
impersonationContext.Undo();
}
There are, of course, conditions. The user must be authenticated (can't be an anonymous user, unless the anonymous user also has access to your web service). Also, the code above is just an example - in production code there are quite a few other things to consider. Here is an msdn article that should help get you started.
FYI, the reason it works in development is most likely because your development server process runs as you, and since you have access to the web service, it succeeds.