We are porting our Windows 8.1 app to UWP and are experiencing an issue with sending default user credentials for single sign-on. It seems that credentials are not being set, despite setting UseDefaultCredentials = true on the handler. This exact code was working in Windows 8.1.
using (var client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true }))
{
// client._handle.Credentials is null
// This call fails authentication with a 401
// and Fiddler shows no attempt to pass authentication
var response = await client.GetStringAsync(new Uri(GlobalConfig.Config.BaseUri, "Presenter/SingleSignOn"));
...
}
As noted above, in debugging I can see that client._handle.Credentials is null.
I have also tried setting credentials from the System.Net.CredentialCache, but these appear to return empty credentials and they also result in a null client._handle.Credentials.
var handler = new HttpClientHandler() { Credentials = System.Net.CredentialCache.DefaultCredentials };
var handler = new HttpClientHandler() { Credentials = System.Net.CredentialCache.DefaultNetworkCredentials };
I have double-checked our declared Capabilities as per this answer and they look fine. We have declared capabilities:
Enterprise Authentication
Internet (client)
Private Networks (Client & Server)
Removable Storage
I have tried using Windows.Web.HttpClient, but it has the same issue--it doesn't find default credentials, so it prompts via a UI. Am I missing something obvious? Or something non-obvious?
TL;DR - I am having trouble passing default user credentials in HttpClient requests.
Edit: I should add that authentication in general is working. If I pass a username/password explicitly, then it works. Obviously, that's not the goal here.
Related
I'm kind of new to the whole WCF and SOAP topic so please be kind.
I'm using a generated SOAP Client with .net6. In another project we successfully worked with the same Web Service using the old .net Framework 2.0 Web References and the same credentials.
Strange enough everything seemed to work fine at first. Until I realized, that it does not use the given credentials to authenticate. Instead it authenticates with my own domain user.
I also tried to get it to work with explicitly setting the binding with a BasicHttpBinding but I only could get the same broken logic to work or I got various authentication/protocol/security errors.
So it seems the authentication is basically working. It just doesn't use the provided credentials. So my question is: How can I configure it to work with the provided identity?
I also found out that it might have anything to do with a cached Windows token. But how can I get rid of it. How to prevent caching in the first place?
EDIT:
Specified the variable types explicitly.
string url = "http://someServer/AdministrationService.asmx";
AdministrationServiceSoapClient client = new AdministrationServiceSoapClient(
AdministrationServiceSoapClient.EndpointConfiguration.AdministrationServiceSoap,
url);
WindowsClientCredential credential = client.ClientCredentials.Windows;
credential.ClientCredential.UserName = "username";
credential.ClientCredential.Password = "password";
credential.ClientCredential.Domain = "DOMAIN";
GetServerInfoRequest getServerInfoRequest = new GetServerInfoRequest
{
// some stuff set here
};
GetServerInfoRequest getServerInfoReply = await client.GetServerInfoAsync(getServerInfoRequest);
As far as I know, BasicHttpBinding has security disabled by default, but can be added setting the BasicHttpSecurityMode to a value other than None in the constructor. It can be configured according to the instructions in BasicHttpBinding and BasicHttpBinding Constructors.
By default, setting up client credentials involves two steps: determining the type of client credential required by the service and specifying an actual client credential, as described in this document.
After waiting a day it is working. It seems that the cached credentials became invalid somehow.
Strange enough the simple service creation from above is not working anymore. Instead I have to use the following.
var client = new AdministrationServiceSoapClient(
new BasicHttpBinding()
{
Security = new BasicHttpSecurity()
{
Mode = BasicHttpSecurityMode.TransportCredentialOnly,
Message = new BasicHttpMessageSecurity()
{
ClientCredentialType = BasicHttpMessageCredentialType.UserName,
},
Transport = new HttpTransportSecurity()
{
ClientCredentialType = HttpClientCredentialType.Windows,
ProxyCredentialType = HttpProxyCredentialType.Windows,
}
},
},
new EndpointAddress(url));
I'm trying to make an http call from website A to website B, using the website A's identity.
Using .Net fwk 4.x, I just have to make something like that:
using (var client = new HttpClient(new HttpClientHandler { UseDefaultCredentials = true }) {
// Do something
}
In aspnetcore 2.2, an IHttpClientBuilder has been provided to manage http clients.
The same code is supposed to look to something like this:
services.AddHttpClient("myOtherSite", httpClient => {
httpClient.BaseAddress = new Uri("http://something");
})
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler {
UseDefaultCredentials = true
});
Then I just need to inject the IHttpBuilder where I need a client then call clientBuilder.buildClient("myOtherSite").
Using this, http request seems to be made without the pool user, despite the documentation..
Has someone done something like this?
I'm not sure if this is your issue but this Microsoft Documentation states that starting with core 2.1, the System.Net.Http.SocketsHttpHandler class is used instead.
It has information on how to configure to allow continuing use of HttpClientHandler but you may want to switch or even try using HttpMessageHandler in place of the type you're using for the handler currently.
I have an ASP.Net core website deployed on Azure app service for Linux.
In the controller, I am trying to get the client certificate like below:
var callerCertificate = Request.HttpContext.Connection.ClientCertificate;
I always get callerCertificate as null.
I have tried await Request.HttpContext.Connection.GetClientCertificateAsync() with same result null.
My website webhost creation looks like below:
WebHost.CreateDefaultBuilder(args)
.UseKestrel()
.UseStartup<Startup>()
.UseSerilog();
I have also set SSL setting for the website (in Azure) as below:
The client side caller is a net462 project that uses Microsoft.Rest.CertificateCredentials to set the certificate to HTTP request.
var cred = new CertificateCredentials(_deviceCertificate)
...
await this.cred.ProcessHttpRequestAsync(_httpRequest, cancellationToken).ConfigureAwait(false);
You could try to add the certificate using HttpClient directly instead of using Microsoft.Rest.CertificateCredential.
var clientHandler = new HttpClientHandler();
clientHandler.ClientCertificateOptions = ClientCertificateOption.Manual;
clientHandler.ClientCertificates.Add(_deviceCertificate);
var client = new HttpClient(clientHandler);
var result = client.GetAsync("https://yourservice").GetAwaiter().GetResult();
You may also need to configure the SSL protocol (SSL2, SSL3, TLS, etc.):
clientHandler.SslProtocols = SslProtocols.Tls;
Answering my own question:
I am able to get the client certificate from header
string clientCertFromHeader = Request.Headers["X-ARR-ClientCert"];
Though, it is still a mystery as to why Request.HttpContext.Connection.ClientCertificate is not giving the certificate.
This question has been discussed in several topics here but I could not find the answer for me.
What I'm trying to do is use an IP camera through the Onvif interface. I've generated the web services from the WSDL files available in the Onvif homepage, and added the custom SOAP authentication code as suggested here, and I am able to retrieve the device capabilities etc. etc.
But for some services, e.g, PTZ control, also HTTP authentication is needed. My code removes the ClientCredentials behaivor (so yeah, I guess setting them does not make any sense, but I still left those lines in hope that maybe the HTTP transport would try to use them):
HttpTransportBindingElement httpBindingElement = new HttpTransportBindingElement();
httpBindingElement.AuthenticationScheme = AuthenticationSchemes.Basic;
...
PTZClient ptzClient = new PTZClient(customBinding, endPointAddress);
ptzClient.Endpoint.Behaviors.Remove(typeof(System.ServiceModel.Description.ClientCredentials));
UsernameClientCredentials onvifCredentials = new UsernameClientCredentials(new UsernameInfo(_username, _password));
ptzClient.Endpoint.Behaviors.Add(onvifCredentials);
ptzClient.ClientCredentials.UserName.UserName = _username;
ptzClient.ClientCredentials.UserName.Password = _password;
Still when I look at wireshark, i see that the SOAP authentication is generated but no HTTP authentication header is set (well, I already expected that since i have a custom behaivor here). So the question is, if I am creating the binding this way, what are my best options to add HTTP authentication headers? Can I just add a message inspector, and if so, any examples? Must I create a different transport binding? I've seen people advising others to use BasicHttpBinding and then setting the Security property on that, but where do the credentials go in that case and how do I apply the BasicHttpBinding instance to my binding? Are there any callbacks in the WCF that get triggered by the HTTP 401 code that i can hook up to and then provide the header? This is actually my first experience with WCF and so far I've done everything from examples found in the internet, but as for this particular issue I haven't been able to find anything.
If anyone is interested this is how I got it working. I combined the BasicHttpBinding with the client credentials in a following way:
TransportSecurityBindingElement transportSecurity = new TransportSecurityBindingElement();
// UsernameCredentials is a class implementing WS-UsernameToken authentication
transportSecurity.EndpointSupportingTokenParameters.SignedEncrypted.Add(new UsernameTokenParameters());
transportSecurity.AllowInsecureTransport = true;
transportSecurity.IncludeTimestamp = false;
TextMessageEncodingBindingElement messageEncoding = new TextMessageEncodingBindingElement(MessageVersion.Soap12, Encoding.UTF8);
HttpClientCredentialType[] credentialTypes = new HttpClientCredentialType[3] { HttpClientCredentialType.None, HttpClientCredentialType.Basic, HttpClientCredentialType.Digest };
...
foreach (HttpClientCredentialType credentialType in credentialTypes)
{
BasicHttpBinding httpBinding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly);
httpBinding.Security.Transport.ClientCredentialType = credentialType;
BindingElementCollection elements = new BindingElementCollection(new BindingElement[1]{messageEncoding});
foreach(BindingElement element in httpBinding.CreateBindingElements())
{
if (element is TextMessageEncodingBindingElement)
continue;
elements.Add(element);
}
CustomBinding customBinding = new CustomBinding(elements);
DeviceClient deviceClient = new DeviceClient(customBinding, endPointAddress);
if (credentialType == HttpClientCredentialType.Basic)
{
// Set all credentials, not sure from which one WCF actually takes the value
deviceClient.ClientCredentials.UserName.UserName = pair[0];
deviceClient.ClientCredentials.UserName.Password = pair[1];
}
else if (credentialType == HttpClientCredentialType.Digest)
{
deviceClient.ClientCredentials.HttpDigest.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Delegation;
deviceClient.ClientCredentials.HttpDigest.ClientCredential.UserName = pair[0];
deviceClient.ClientCredentials.HttpDigest.ClientCredential.Password = pair[1];
}
}
This works efficiently with a device for which we do not know the authentication mode and works on both (HTTP/SOAP) authentication level.
I detailed how HTTP digest works in another answer.
Remember that only functions of class PRE_AUTH, according to ยง5.12.1 of the Core spec, require authentication.
You should invoke a function of any class but PRE_AUTH without any form authentication. If you get a HTTP 401 then you have to use HTTP digset, otherwise you'll have to got with WS-UsernameToken.
You can't directly use HTTP digest because you'll need at least the device to send you the challange for HTTP digest.
I'm trying to supply specific domain credentials to access reports on my reporting server. If I use default credentials (don't supply actual credits) it works just fine as I have access to my report server. As soon as I hardcode my credentials (and disable default credentials) it gives me a 401 exception (The request failed with HTTP status 401: Unauthorized.)
comboBox1 is a combobox (obviously) with each server available
urlText is a text field for the web service url
comboBox1.Items.Clear();
var rs = new ReportingService2005
{
Url = urlText.Text,
//any of those three work
//Credentials = System.Net.CredentialCache.DefaultCredentials,
//Credentials = System.Net.CredentialCache.DefaultNetworkCredentials
UseDefaultCredentials = true
//This doesn't work
//Credentials = new NetworkCredential("user", "pass", "domain")
};
// get catalog items from the report server database
var items = rs.ListChildren("/", true); <------exception on this line---------
foreach (var item in items)
{
if (item.Type == ItemTypeEnum.Report)
comboBox1.Items.Add(item.Path);
}
As far as I can tell, that should be all that's needed. It could be something to do with the rsreportserver.config. As it is now, going through the web interface doesn't prompt for credentials (I don't know if that's important or not, just more information).
Here's the authentication section
<Authentication>
<AuthenticationTypes>
<RSWindowsNegotiate/>
</AuthenticationTypes>
<RSWindowsExtendedProtectionLevel>Off</RSWindowsExtendedProtectionLevel>
<RSWindowsExtendedProtectionScenario>Proxy</RSWindowsExtendedProtectionScenario>
<EnableAuthPersistence>true</EnableAuthPersistence>
</Authentication>