Given is a wcf rest service which runs with HttpClientCredentialType.Windows and enforces a user to authenticate via kerberos.
private static void Main(string[] args)
{
Type serviceType = typeof (AuthService);
ServiceHost serviceHost = new ServiceHost(serviceType);
WebHttpBinding binding = new WebHttpBinding();
binding.Security.Mode = WebHttpSecurityMode.TransportCredentialOnly;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
ServiceEndpoint basicServiceEndPoint = serviceHost.AddServiceEndpoint(typeof(IAuthService), binding, "http://notebook50:87");
basicServiceEndPoint.Behaviors.Add(new WebHttpBehavior());
Console.WriteLine("wcf service started");
serviceHost.Open();
Console.ReadLine();
}
public class AuthService : IAuthService
{
public List<string> GetUserInformation()
{
List<string> userInfo = new List<string>();
userInfo.Add("Environment.User = " + Environment.UserName);
userInfo.Add("Environment.UserDomain = " + Environment.UserDomainName);
if (OperationContext.Current != null && OperationContext.Current.ServiceSecurityContext != null)
{
userInfo.Add("WindowsIdentity = " + OperationContext.Current.ServiceSecurityContext.WindowsIdentity.Name);
userInfo.Add("Auth protocol = " + OperationContext.Current.ServiceSecurityContext.WindowsIdentity.AuthenticationType);
}
else
{
userInfo.Add("WindowsIdentity = empty");
}
WebOperationContext.Current.OutgoingResponse.ContentType = "text/plain";
return userInfo;
}
}
[ServiceContract]
public interface IAuthService
{
[OperationContract]
[WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json, UriTemplate = "test/")]
List<string> GetUserInformation();
}
When i run this as a console application, and then open the website http://notebook50:87/test/ in internet explorer from another computer, i get a 'bad request' response.
I did enable kerberos logging, and it shows me KDC_ERR_PREAUTH_REQUIRED
I can solve this problem by creating a windows service, and run it under 'Local System account'.
In this case, a client is able to authenticate.
Question: What permission/settings does a user(which runs this wcf service) need in order to get the same behavior as when the application is running as windows service under local system?
Is this related with the Service Principle Name?
It is working now.
It really was a problem with the SPN
At the beginning, I've set the SPN like setpn -A HTTP/notebook50.foo.com, and with this, the kerberos authentication didn't work.
Now, i've set it like setspn -A HTTP/notebook50.foo.com username where username is the user under which the service runs.
From the SPN documentation i've read, it was not clear to me that i have to set the user account in this way.
It would be great if one could explain what happens here, and probably a link to a documentation for this scenario.
You can stop this error pops up via enable the "Do not require Kerberos preauthentication" option for that user account in Active directory users & computers -> properties -> account.
Related
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.
I have a C# app which is calling a WCF service via a proxy server.
Both the proxy and the WCF service require (different) credentials.
I am using the svcutil-generated proxy classes to access the WCF service.
When I switch off the proxy server's requirement for credentials then I can access the WCF service fine so all that I want is to know where to apply the credentials for the proxy server (I'm using Fiddler in my dev environment).
I've read various posts which say to set the credentials in client.ClientCredentials and this seems to work for either the proxy server credentials OR the WCF service credentials but I can't store both.
If I put the proxy server credentials in client.ClientCredentials then the request hits the WCF service but then gets rejected.
If I put the WCF service credentials in client.ClientCredentials then the request does not get past the proxy server.
Is there a way to supply two different sets of credentials so I can get authenticated by the proxy server and the WCF service? I need to do this in code, not in the config file.
Code is below:
public class Runner
{
public int GetAvailableFacilities(int timeoutInSeconds, string uri, string userName, string password)
{
FacilitySearchServiceClient client = null;
try
{
client = SetClient(timeoutInSeconds, uri, userName, password, client);
string outputMessage = null;
int[] availableFacilities = client.GetAvailableFacilities(out outputMessage);
return availableFacilities.Length;
}
finally
{
if (client != null)
{
client.Close();
}
}
}
private FacilitySearchServiceClient SetClient(int timeoutInSeconds, string uri, string wcfServiceUserName, string wcfServicePassword, FacilitySearchServiceClient client)
{
string proxyServerUsername = "1";//Fiddler
string proxyServerPassword = "1";//Fiddler
client = new FacilitySearchServiceClient(ConfigureServiceBinding(timeoutInSeconds), new EndpointAddress(uri));
//If this is the only uncommented call to SetServiceCredentials the proxy server transmits the request
//to the wcf service which then rejects the authentication attempt.
SetServiceCredentials(client.ClientCredentials, proxyServerUsername, proxyServerPassword);
//If this is the only uncommented call to SetServiceCredentials the proxy server rejects the authentication attempt,
//resulting in an EndpointNotFoundException.
SetServiceCredentials(client.ClientCredentials, wcfServiceUserName, wcfServicePassword);
return client;
}
protected static void SetServiceCredentials(ClientCredentials credentials, string userName, string password)
{
credentials.UserName.UserName = userName;
credentials.UserName.Password = password;
}
protected CustomBinding ConfigureServiceBinding(int timeoutInSeconds)
{
CustomBinding binding = new CustomBinding();
SecurityBindingElement sbe = SecurityBindingElement.CreateUserNameOverTransportBindingElement();
sbe.IncludeTimestamp = false;
sbe.EnableUnsecuredResponse = true;
sbe.AllowInsecureTransport = true;
binding.Elements.Add(sbe);
binding.Elements.Add(new TextMessageEncodingBindingElement(MessageVersion.Soap11, System.Text.Encoding.UTF8));
binding.Elements.Add(CreateHttpsBindingElement(Const.ProxyServerUrl));
return binding;
}
/// <summary>
/// Sets up the element needed for the web service call.
/// </summary>
private static HttpsTransportBindingElement CreateHttpsBindingElement(string proxyUri)
{
HttpsTransportBindingElement tpt = new HttpsTransportBindingElement();
tpt.TransferMode = TransferMode.Streamed;
tpt.MaxReceivedMessageSize = Int32.MaxValue;
tpt.MaxBufferSize = Int32.MaxValue;
tpt.AuthenticationScheme = System.Net.AuthenticationSchemes.Anonymous;
if (string.IsNullOrEmpty(proxyUri) == false)
{
tpt.ProxyAddress = new Uri(proxyUri);
tpt.UseDefaultWebProxy = false;
tpt.ProxyAuthenticationScheme = System.Net.AuthenticationSchemes.Basic;
}
return tpt;
}
}
I ran in to this exact same problem a few months back. The problem is the WCF client proxy does not expose a separate property for specifying proxy server credentials like say, the HttpWebRequest object which exposes a proxy property of type System.Net.WebProxy. In that case you could just use the Credentials property of the System.Net.WebProxy type to achieve what you are trying to do. In your case you can do this for your WCF client by setting the proxy credentials for all the web requests made by your .NET application by modifying the Application_Start method in Global.asax.cs (assuming that your client is also a web app). Otherwise you could just use this code in the appropriate startup method of your client application.
protected void Application_Start(Object sender, EventArgs e)
{
var proxy = new WebProxy("myproxyservername", <myproxyPort>) { Credentials = new System.Net.NetworkCredential("yourproxyusername", "yourproxypassword") };
WebRequest.DefaultWebProxy = proxy;
}
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.
I have Wcf rest service.
I host it in console application.
when I try to access the service from my local computer it works but when I try from remote
computer the service is not found.
here is the code:
service definition:
[ServiceContract]
public interface IInstagramCallbackService
{
[OperationContract]
[WebInvoke(Method = "GET", UriTemplate = "url/?hub.mode={mode}&hub.challenge={challenge}&hub.verify_token={token}")]
string CheckServiceAvailability(string mode, string challenge, string token);
}
public class InstagramCallbackService : IInstagramCallbackService
{
public string CheckServiceAvailability(string mode, string challenge, string token)
{
return challenge;
}
}
hosting:
ServiceHost host = new ServiceHost(typeof(InstagramCallbackService),new Uri[]{});
WebHttpBinding binding = new WebHttpBinding(WebHttpSecurityMode.None);
ServiceEndpoint endPoint = new ServiceEndpoint(
ContractDescription.GetContract(
typeof(InstagramCallbackService)), binding, new EndpointAddress(
"http://127.0.0.1:6064/InstagramCallbackService"));
WebHttpBehavior webBehavior = new WebHttpBehavior();
endPoint.Behaviors.Add(webBehavior);
host.AddServiceEndpoint(endPoint);
host.Open();
Console.WriteLine("ready");
Console.ReadLine();
127.0.0.1 is a local address. It's like saying localhost or "this computer". You can't hit 127.0.0.1 from another computer, because 127.0.0.1 from another computer would just be that computer. Surely your machine has other addresses you can use though no? Try ipconfig from the command line.
I have tried both the methods below but both return the result "The Autodiscover service couldn't be located."
http://msdn.microsoft.com/en-us/library/gg591267(v=EXCHG.140).aspx
service.Credentials = new NetworkCredential(userData.EmailAddress, userData.Password);
if (userData.AutodiscoverUrl == null)
{
service.AutodiscoverUrl(userData.EmailAddress, RedirectionUrlValidationCallback);
userData.AutodiscoverUrl = service.Url;
}
else
{
service.Url = userData.AutodiscoverUrl;
}
return service;
}
http://code.msdn.microsoft.com/Exchange-2013-Set-pull-14c8360b#content
static ExchangeService GetBinding()
{
// Create the binding.
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP1);
// Define credentials.
service.Credentials = new WebCredentials("myemail#work.com", "password");
// Use the AutodiscoverUrl method to locate the service endpoint.
try
{
service.AutodiscoverUrl("myemail#work.com", RedirectionUrlValidationCallback);
}
catch (AutodiscoverRemoteException ex)
{
Console.WriteLine("Exception thrown: " + ex.Error.Message);
}
// Display the service URL.
Console.WriteLine("AutodiscoverURL: " + service.Url);
return service;
}
In the one instance I enter my email and password, in the other its hard-coded. Both hang when attempting Autodiscoverurl and eventually fail with the message "The Autodiscover service couldn't be located." I added the references as per the tutorials and Autodiscover appears under Microsoft.Exchange.WebServices.dll ... Is there something else I'm missing?
try adding:
service.UseDefaultCredentials = false;
after you set the credentials
I don't know if you have resolved it yet, I bumped into your post as I was searching for a solution for the same (or similar?) problem.
The peculiar thing was:
Since it worked perfectly in Debug mode, but not once installed as Windows service, I changed the 'Log On' settings in the Windows Service Properties (services.msc, right click on installed service, Properties, Log On Tab) and set it to 'Local System account' and checked the 'Allow service to interact with desktop' option.
That did the trick for me.