Using NetworkCredential in WCF services - c#

I have a WCF service which uses Windows Authentication to view Service Contract and a specfic method in a service is configured to be accessed only by a specific user UserX.
[PrincipalPermission(SecurityAction.Demand,Name="xxx\\UserA")]
In the client side, I need to access the above service method. If I am using a Web Reference -> I add the following
client = new WebRefLocal.Service1();
client.Credentials = new System.Net.NetworkCredential("UserA", "xxxxxx", "test");
But the above cannot be achieved in WCF Service Reference as Client Credentials are read-only. One best way I can achieve the above is Impersonation
https://msdn.microsoft.com/en-us/library/ff649252.aspx.
My question here is
Why ClientCredentials are made readonly in WCF?
How Network Credential work? Will they authenticate the Windows login in client side or server side?
Is there is any way I can achieve the above in WCF aswell without impersonation?

I've done something like this - hope it helps:
var credentials = new ClientCredentials();
credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Delegation;
credentials.Windows.ClientCredential = new System.Net.NetworkCredential("UserA", "xxxxxx", "test");
client.Endpoint.Behaviors.Remove<ClientCredentials>();
client.Endpoint.Behaviors.Add(credentials);
Used with a BasicHttpBinding with following security settings:
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows" proxyCredentialType="Windows" />
</security>

One method you can use is to make use of ChannelFactory when calling the WCF service.
The following code was taken from one of my MVCprojects, hence it has someModelState` validation code, I'm sure you can modify it to suit your needs.
protected R ExecuteServiceMethod<I, R>(Func<I, R> serviceCall) {
R result = default(R);
ChannelFactory<I> factory = CreateChannelFactory<I>();
try {
I manager = factory.CreateChannel();
result = serviceCall.Invoke(manager);
} catch (FaultException<ValidationFaultException> faultException) {
faultException.Detail.ValidationErrors.ToList().ForEach(e => ModelState.AddModelError("", e));
} finally {
if (factory.State != CommunicationState.Faulted) factory.Close();
}
return result;
}
private ChannelFactory<I> CreateChannelFactory<I>() {
UserAuthentication user = GetCurrentUserAuthentication();
ChannelFactory<I> factory = new ChannelFactory<I>("Manager");
if (IsAuthenticated) {
factory.Credentials.UserName.UserName = user.UserName;
factory.Credentials.UserName.Password = user.Password;
}
BindingElementCollection elements = factory.Endpoint.Binding.CreateBindingElements();
factory.Endpoint.Binding = new CustomBinding(elements);
SetDataContractSerializerBehavior(factory.Endpoint.Contract);
return factory;
}

Related

Share windows identity between WCF services with WSDualHttpBinding

I have two WCF services hosted separately in IIS 7. The first service is callable from outside and uses a WebHttpBinding with windows authentication. The second service is only called by the first one, using a WsDualHttpBinding.
When the first service is called, I can get the user's windows name from ServiceSecurityContext.Current.WindowsIdentity.Name. In the second service, that doesn't work and ServiceSecurityContext.Current.WindowsIdentity.Name is just IIS APPPOOL\DefaultAppPool.
I configured the WsDualHttpBinding to use windows authentication, but that didn't help. Here is the server-side configuration:
<wsDualHttpBinding>
<binding name="internalHttpBinding">
<security mode="Message">
<message clientCredentialType="Windows"/>
</security>
</binding>
</wsDualHttpBinding>
And here's the code in the first service to establish communication with the second service:
private WSDualHttpBinding binding = new WSDualHttpBinding();
private ChannelFactory<IMyService> factory;
public IMyService Contract { get; set; }
public MyServiceCallback Callback { get; set; }
public MyService(Uri uri)
{
EndpointAddress address = new EndpointAddress(uri);
Callback = new MyServiceCallback();
var instanceContext = new InstanceContext(Callback);
binding.Security.Mode = WSDualHttpSecurityMode.Message;
binding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;
factory = new DuplexChannelFactory<IMyService>(instanceContext, binding, address);
factory.Credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;
factory.Credentials.Windows.ClientCredential = CredentialCache.DefaultNetworkCredentials;
Contract = factory.CreateChannel();
// Call operations on Contract
}
How can I configure the first service to pass on the user's identity to the second service?
This seems to be a problem with pass-through authentication.
First, you need to be in a Active Directory environment.
Kerberos must be used for authentication, NTLM will not work. You can use klist to check this:
https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/klist
Also see
https://blogs.msdn.microsoft.com/besidethepoint/2010/05/08/double-hop-authentication-why-ntlm-fails-and-kerberos-works/
for an explanation.
May be this SO article can help:
Pass Windows credentials to remote https WCF service
And this:
https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/delegation-and-impersonation-with-wcf
After the server-side enables impersonation and the client-side has set up the windows credential,
ServiceReference1.ServiceClient client = new ServiceReference1.ServiceClient();
client.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
client.ClientCredentials.Windows.ClientCredential.UserName = "Test";
client.ClientCredentials.Windows.ClientCredential.Password = "123456";
We could retrieve the running Windows account by using the below code.
if (ServiceSecurityContext.Current.WindowsIdentity.ImpersonationLevel == TokenImpersonationLevel.Impersonation ||
ServiceSecurityContext.Current.WindowsIdentity.ImpersonationLevel == TokenImpersonationLevel.Delegation)
{
using (ServiceSecurityContext.Current.WindowsIdentity.Impersonate())
{
Console.WriteLine("Impersonating the caller imperatively");
Console.WriteLine("\t\tThread Identity :{0}",
WindowsIdentity.GetCurrent().Name);
Console.WriteLine("\t\tThread Identity level :{0}",
WindowsIdentity.GetCurrent().ImpersonationLevel);
Console.WriteLine("\t\thToken :{0}",
WindowsIdentity.GetCurrent().Token.ToString());
}
}
Please refer to the below example.
https://learn.microsoft.com/en-us/dotnet/framework/wcf/samples/impersonating-the-client
https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/delegation-and-impersonation-with-wcf
Feel free to let me know if there is anything I can help with.

.Net Core 2.0 call to WCF client configuration

I have a .NET Core 2.0 application and need to call a WCF client from one of its controllers, and pass the user credentials for authentication.
Within the .net core app I created a reference for the WCF client using the Connected Services (WCF Web Service Reference Provider) and now in a process of configuring the call. Note that I can use the same endpoint form a 4.6 framework application without any problems.
Here's my code:
var binding = new BasicHttpBinding {Security = {Mode = BasicHttpSecurityMode.Transport}};
var address = new EndpointAddress("https://my-endpoint.asmx");
var client = new MyAppSoapClient(binding, address);
var credentials = CredentialCache.DefaultNetworkCredentials;
client.ClientCredentials.Windows.ClientCredential = credentials;
client.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
var response = client.GetStuff("param").Result;
I face a number of problems:
It has to be a https call
I need to pass the currently log in user credentials to the call
The current error I get is as follows:
The HTTP request is unauthorized with client authentication scheme 'Anonymous'. The authentication header received from the server was 'Negotiate, NTLM'
Also the ConnectedService.json (created automativcally by WCF Web Service Reference Provider) has a predefined endpoint Uri.. I don't understand why I need to pass the address to the client manually (the code seems to be forcing me to do so).. ideally I'd like to get this dynamically amended in json depending on environment.
Thanks.
I noticed that you passed the current logged-in user as a Windows credential (which is also necessary for enabling impersonation), but you did not explicitly set the client credentials for the transport layer security.
BasicHttpBinding binding = new BasicHttpBinding();
binding.Security.Mode = BasicHttpSecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
Also the ConnectedService.json (created automativcally by WCF Web
Service Reference Provider) has a predefined endpoint Uri.. I don't
understand why I need to pass the address to the client manually (the
code seems to be forcing me to do so)
You can modify the method of automatic generation of proxy client to construct client proxy class (located in the reference.cs)
Modify the binding security
private static System.ServiceModel.Channels.Binding GetBindingForEndpoint(EndpointConfiguration endpointConfiguration)
{
if ((endpointConfiguration == EndpointConfiguration.WebService1Soap))
{
System.ServiceModel.BasicHttpBinding result = new System.ServiceModel.BasicHttpBinding();
result.Security.Mode = System.ServiceModel.BasicHttpSecurityMode.Transport;
result.Security.Transport.ClientCredentialType = System.ServiceModel.HttpClientCredentialType.Windows;
result.MaxBufferSize = int.MaxValue;
result.ReaderQuotas = System.Xml.XmlDictionaryReaderQuotas.Max;
result.MaxReceivedMessageSize = int.MaxValue;
result.AllowCookies = true;
return result;
}
Modify the endpoint.
private static System.ServiceModel.EndpointAddress GetEndpointAddress(EndpointConfiguration endpointConfiguration)
{
if ((endpointConfiguration == EndpointConfiguration.WebService1Soap))
{
return new System.ServiceModel.EndpointAddress("http://10.157.13.69:8001/webservice1.asmx");
Construct the client proxy class.
ServiceReference1.WebService1SoapClient client = new WebService1SoapClient(WebService1SoapClient.EndpointConfiguration.WebService1Soap);
client.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
client.ClientCredentials.Windows.ClientCredential.UserName = "administrator";
client.ClientCredentials.Windows.ClientCredential.Password = "123456";
Feel free to let me know if there is anything I can help with.
My binding was missing the security Ntlm credential type (see below).
Problem solved.
var binding = new BasicHttpBinding {Security = {Mode = BasicHttpSecurityMode.Transport,
Transport = new HttpTransportSecurity(){ClientCredentialType = HttpClientCredentialType.Ntlm } }};

Testing Authentication For Self-Hosted WCF Service in an Integration Test

I am trying to consume a self-hosted WCF application using SSL and a custom authentication validator from within an integration test. So far I am able to self-host the service but I am not able to figure out how to consume it.
Here is the self-hosting code (it is not dependent on Web.Config, as far as I know):
[ClassInitialize]
public static void TestClassInitialize(TestContext testContext)
{
const string serviceAddress = "https://localhost/SelfHostedService";
Uri _svcEndpointUri = new Uri(serviceAddress);
var binding = new WSHttpBinding
{
Security =
{
Mode = SecurityMode.TransportWithMessageCredential,
Message = {ClientCredentialType = MessageCredentialType.UserName}
}
};
ServiceDebugBehavior debugBehavior = new ServiceDebugBehavior
{
IncludeExceptionDetailInFaults = true
};
MyServiceApi _api = new MyServiceApi();
ServiceHost _svcHost = new ServiceHost(_api, _svcEndpointUri);
_svcHost.Description.Behaviors.Remove<ServiceDebugBehavior>();
_svcHost.Description.Behaviors.Add(debugBehavior);
// Ensure that SSL certificate & authentication interceptor get used
ServiceCredentials credentials = new ServiceCredentials();
credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new MyCustomAuthenticationValidator();
credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySubjectName, "SubjectName");
_svcHost.Description.Behaviors.Remove<ServiceCredentials>();
_svcHost.Description.Behaviors.Add(credentials);
// Add IUbiquity and mex endpoints
Uri endpointAddress = new Uri(serviceAddress + "/UbiquityApi.svc");
_svcHost.AddServiceEndpoint(typeof (IUbiquityApi), binding, endpointAddress);
// Specify InstanceContextMode, which is required to self-host
var behavior = _svcHost.Description.Behaviors.Find<ServiceBehaviorAttribute>();
behavior.InstanceContextMode = InstanceContextMode.Single;
_svcHost.Open();
}
What I'd like to be able to do looks like this, but I have no idea how I'd go about accomplish this:
[TestMethod]
public void TestAuthentication(){
var api = _svcHost.MagicallyRetrieveServiceInstance();
api.Credentials = new MagicCredentials("my username", "my password");
Assert.AreEqual(3, api.AddNumbers(1,2));
// Also assert that I am authenticated
api.Credentials = new MagicCredentials("my username", "my password");
bool exceptionWasThrown = false;
try {
api.AddNumbers(1,2);
}
catch(NotLoggedInException l){ // or something
exceptionWasThrown = true;
}
Assert.IsTrue(exceptionWasThrown);
}
My ideal solution would allow me to retrieve the service contract from the service host, and allow me to set the credentials used for the service contract. I should only have to supply the credentials once to the service contract, and then I should be able to call methods directly, as if I were communicating over the wire (thus making this an integration test). How should I go about this?
To consume the web service, simply add the service as a service reference, and then use the service reference client.
Done right, this will take care of the bindings needed for authentication, effectively putting the WCF configurations under test.

Using C# Service Reference with Proxy

I am trying to use a ServiceReference in a C# project.
The project is intended to test a connection. I have a customer who is trying to connect with a C# application to one web service of one of my colleagues. The connection can't be established and he is assuming that it is our own mistake.
I am trying to write now a simple C# project.
Thats enough of story... now the Information needed.
The customer uses a Proxy server
I try to connect to this web service for testing reasons
http://www.w3schools.com/webservices/tempconvert.asmx
I Use .Net Framework 4.0
My Project compiles into a Windows Forms application.
Here The source code of my Method:
private void button2_Click(object sender, EventArgs e)
{
try
{
//Create Client
ServiceReference1.TempConvertSoapClient client = new ServiceReference1.TempConvertSoapClient(#"TempConvertSoap",#"http://www.w3schools.com/webservices/tempconvert.asmx");
if (client.ClientCredentials != null)
{
//Use Values which are typed in in the GUI
string user = tbUser.Text;
string password = tbPassword.Text;
string domain = tbDomain.Text;
//Check what information is used by the customer.
if (!string.IsNullOrEmpty(user) && !string.IsNullOrEmpty(password) && !string.IsNullOrEmpty(domain))
{
client.ClientCredentials.Windows.ClientCredential = new NetworkCredential(user, password, domain);
}
if (!string.IsNullOrEmpty(user) && !string.IsNullOrEmpty(password))
{
client.ClientCredentials.Windows.ClientCredential = new NetworkCredential(user, password);
}
}
//Oh nooo... no temperature typed in
if (string.IsNullOrEmpty(tbFahrenheit.Text))
{
//GOOD BYE
return;
}
//Use the webservice
string celsius = client.FahrenheitToCelsius(tbFahrenheit.Text); //<-- Simple Calculation
tbCelsius.Text = celsius;
}
catch(Exception ex)
{
//Something
MessageBox.Show(ex.ToString());
}
}
Here is my Question:
How can i set a proxy to this service reference or rather the client ?
There is no property or setter for this purpose. I tried it with the ClientCredentials
Okay i found the answer myself.
Hopefully it will help somebody ;).
This part creates a binding. This can later be used in the webservice
private void button2_Click(object sender, EventArgs e)
{
BasicHttpBinding binding = new BasicHttpBinding("TempConvertSoap");
if (!string.IsNullOrEmpty(tbProxy.Text))
{
binding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.Basic;
string proxy = string.Format("http://{0}", tbProxy.Text);
if (!string.IsNullOrEmpty(tbPort.Text))
{
proxy = string.Format("{0}:{1}",proxy,tbPort.Text);
}
binding.UseDefaultWebProxy = false;
binding.ProxyAddress = new Uri(proxy);
}
EndpointAddress endpoint = new EndpointAddress(#"http://www.w3schools.com/webservices/tempconvert.asmx");
Here begins the old part where i set the binding .
try
{
//Create Client
ServiceReference1.TempConvertSoapClient client = new ServiceReference1.TempConvertSoapClient(binding, endpoint);
if (client.ClientCredentials != null)
{
//Use Values which are typed in in the GUI
string user = tbUser.Text;
string password = tbPassword.Text;
string domain = tbDomain.Text;
client.ClientCredentials.UserName.UserName = user;
client.ClientCredentials.UserName.Password = password;
client.ClientCredentials.Windows.ClientCredential.Domain = domain;
//Check what information is used by the customer.
if (!string.IsNullOrEmpty(user) && !string.IsNullOrEmpty(password) && !string.IsNullOrEmpty(domain))
{
client.ClientCredentials.Windows.ClientCredential = new NetworkCredential(user, password, domain);
}
if (!string.IsNullOrEmpty(user) && !string.IsNullOrEmpty(password))
{
client.ClientCredentials.Windows.ClientCredential = new NetworkCredential(user, password);
}
}
//Oh nooo... no temperature typed in
if (string.IsNullOrEmpty(tbFahrenheit.Text))
{
//GOOD BYE
return;
}
//Use the webservice
//THIS ONE IS IMPORTANT
System.Net.ServicePointManager.Expect100Continue = false;
string celsius = client.FahrenheitToCelsius(tbFahrenheit.Text); //<-- Simple Calculation
tbCelsius.Text = celsius;
}
catch(Exception ex)
{
//Something
tbCelsius.Text = ex.Message;
MessageBox.Show(ex.ToString());
}
}
I used Squid as my proxy and use a firewall besides the proxy.
After i configured it succesfully i encountered the error (417) Expectation failed.
During the research i found a line of code which helped me to "resolve" this problem
System.Net.ServicePointManager.Expect100Continue = false;
It's and old question but still relevant. The accepted answer works, but I managed to resolve this with a different approach. When you add service reference to your project, Visual Studio will add bindings and endpoint addresses etc to web.config/app.config (depending on the application type). You can add the proxy configuration (ip and port) directly to the config file so that you won't need to do anything special on the code side:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="MyServiceBinding"
proxyAddress="http://123.123.12.1:80" useDefaultWebProxy="false" />
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://address.com/endpoint"
binding="basicHttpBinding" bindingConfiguration="MyServiceBinding"
contract="..." name="..." />
</client>
</system.serviceModel>
So change the ip and port to your proxy servers ip and port and remember to use useDefaultWebProxy="false" so that the application will use that proxy with this service reference.
Reference for basicHttpBinding.

SharePoint GetGroupCollectionFromUser Web Service Returns `HTTP request was forbidden` error

I want to get the list of all groups that a user belongs to in SharePoint using a console application. I'm new to SharePoint dev, so here is my admittedly amateurish attempt.
private static string GetUserGroupCollection(string LoginName)
{
UserGroupSoapClient ug = new UserGroupSoapClient();
ug.ClientCredentials.Windows.ClientCredential.UserName = "myusername";
ug.ClientCredentials.Windows.ClientCredential.Password = "mypassword";
return ( ug.GetGroupCollectionFromUser(LoginName)).ToString();
}
I have included a "Service Reference" to my SP web service available at http://server02/aaa/DOCS/_vti_bin/usergroup.asmx
When I try the code above I get The HTTP request was forbidden with client authentication scheme 'Anonymous'.
Can you please show me a basic example of how this can be done? Please note that I do not want to make the reference as "Web Reference". Thank you.
If your SharePoint WebServices web application use NTLM authentication you can try this
ug.ClientCredentials.Windows.ClientCredential = new NetworkCredential("myusername", "mypassword");
and in your app.config
<security mode="Transport">
<transport clientCredentialType="Ntlm" proxyCredentialType="None" realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
Edited:
Because NTLM authentication is disabled in you web application for access to web services you must first login with Authentication.asmx web service and retrieve the authentication cookie and send it with other web services calls like this:
string cookie = "";
LoginResult loginResult;
string result;
AuthenticationSoapClient authentication = new AuthenticationSoapClient();
UserGroupSoapClient ug = new UserGroupSoapClient();
using(OperationContextScope scope = new OperationContextScope(authentication.InnerChannel))
{
loginResult = authentication.Login("user", "pass");
if (loginResult.ErrorCode == LoginErrorCode.NoError)
{
MessageProperties props = OperationContext.Current.IncomingMessageProperties;
HttpResponseMessageProperty prop = props[HttpResponseMessageProperty.Name] as HttpResponseMessageProperty;
string cookies = prop.Headers["Set-Cookie"];
// You must search cookies to find cookie named loginResult.CookieName and set its value to cookie variable;
cookie = cookies.Split(';').SingleOrDefault(c => c.StartsWith(loginResult.CookieName));
}
}
HttpRequestMessageProperty httpRequestProperty = new HttpRequestMessageProperty();
httpRequestProperty.Headers.Add(System.Net.HttpRequestHeader.Cookie, cookie);
using (new OperationContextScope(ug.InnerChannel))
{
OperationContext.Current.OutgoingMessageProperties.Add(HttpRequestMessageProperty.Name, httpRequestProperty);
result = ug.GetGroupCollectionFromUser(LoginName).ToString();
}
and make sure in app.config allowCookies properties of all binding be false.
<basicHttpBinding>
<binding name="AuthenticationSoap" allowCookies="false"/>
<binding name="UserGroupSoap" allowCookies="false"/>
</basicHttpBinding>
Something like the following should work: (Taken / Edited from http://social.msdn.microsoft.com/Forums/en-US/sharepointdevelopment/thread/b17ae5c8-f845-4cee-8298-c4f7b5c52b57)
I think you should use SPGroup to get all the groups from site and find the user in all groups:
For single user and if you know the group name then you can try with this code:
SPWeb site = SPContext.Current.Web;
SPGroup groupToQuery = site.Groups["Project"];
bool istrue = site.IsCurrentUserMemberOfGroup(groupToQuery);
If you don't know the group name then try with below code (This is not tested by me)
using (SPWeb rootWeb = topLevelSite.OpenWeb())
{
foreach (SPGroup group in rootWeb.Groups)
{
bool isUserInGroup = IsUserInGroup(group, currentUser.LoginName);
}
}
private Boolean IsUserInGroup(SPGroup group, string userLoginName)
{
bool userIsInGroup = false;
try
{
SPUser x = group.Users[userLoginName];
userIsInGroup = true;
}
catch (SPException)
{
userIsInGroup = false;
}
return userIsInGroup;
}

Categories