Flurl has methods for doing OAuth and Basic authentication:
await url.WithBasicAuth("username", "password").GetJsonAsync();
await url.WithOAuthBearerToken("mytoken").GetJsonAsync();
but how do I do Windows authentication using the currently logged in user? The HttpClientHandler that Flurl is built on top of has a property UseDefaultCredentials but I don't know how to utilize that within Flurl.
var httpClient = new HttpClient(new HttpClientHandler()
{
UseDefaultCredentials = true
});
Flurl intelligently reuses the HttpClientHandler for each domain, so you don't want to set the UseDefaultCredentials each time it runs. Instead, you can modify the HttpClientFactory to return one that's configured to UseDefaultCredentials.
public class UseDefaultCredentialsClientFactory : DefaultHttpClientFactory
{
public override HttpMessageHandler CreateMessageHandler()
{
return new HttpClientHandler { UseDefaultCredentials = true };
}
}
Then you need to tell Flurl to use this factory for the domains you want to use Windows authentication for.
public static class FlurlConfiguration
{
public static void ConfigureDomainForDefaultCredentials(string url)
{
FlurlHttp.ConfigureClient(url, cli =>
cli.Settings.HttpClientFactory = new UseDefaultCredentialsClientFactory());
}
}
Then you simply need to call this once on startup for each domain. For ASP.NET, the Application_Start method in your global application class is a good place for it.
FlurlConfiguration.ConfigureDomainForDefaultCredentials("https://example.com");
FlurlConfiguration.ConfigureDomainForDefaultCredentials("http://services.example.com");
Credit goes to Todd Menier for explaining this to me.
If it is still relevant. You can set credentials, something like this
((HttpClientHandler)url.Client.HttpMessageHandler).Credentials = new NetworkCredential(userName, password);
Create HttpClientHandler with your credentials and pass it to HttpClient then to FlurlClient like this:
var clientHandler = new HttpClientHandler
{
Credentials = new NetworkCredential("admin",
"bm8gcGFzc3dvcmQgaGVyZSB5b3UgZnVja2VyIQ==")
};
var httpClient = new HttpClient(clientHandler);
var client = new FlurlClient(httpClient);
var resp = await url.WithClient(client).GetAsync();
Related
I have a Microsoft.Rest.ServiceClient generated with autorest. And I want to access a REST API secured with Windows Authentication and Basic Authentication.
The goal is to use Windows Authentication. I tried it as follows:
var handler = new HttpClientHandler
{
UseDefaultCredentials = true,
};
this.InitializeHttpClient(handler);
This does not work, I get:
System.Net.Http.HttpRequestException: An error occurred while sending the request.
---> System.Net.WebException: The remote server returned an error: (401) Unauthorized.
---> System.ComponentModel.Win32Exception: The target principal name is incorrect
When I use Basic Authentication it works.
this.Credentials = new BasicAuthenticationCredentials
{
UserName = Configuration.User,
Password = Configuration.Password
};
This setup of the ServiceClient is done in the constructor of
MyClient : Microsoft.Rest.ServiceClient
What do I need to add to the client to get Windows Authentication working?
Edited:
It looks like the problem is on server side. Settings in IIS.
The client would work as expected.
This basically reiterates what's already covered in the OP and by #Anders, in my preferred syntax...
var windowsAuthHandler = new HttpClientHandler { UseDefaultCredentials = true };
var webApiUri = new System.Uri("https://localhost:8080");
var apiClient = new MyAutoRestClient(webApiUri ,windowsAuthHandler);
If you're skimming, the OP seems to indicate this doesn't work, when, indeed it does. But, as the OP later states, be sure to start with IIS to make sure it's configured right
I use a similar solution for passing on Windows credentials, and it works nicely.
The only difference is that I use the constructor overload of ServiceClient that takes a HttpClientHandler instance, rather than calling InitializeHttpClient() and it looks something like this:
public class MyClient : ServiceClient<MyClient>
{
public MyClient() : base(new HttpClientHandler { UseDefaultCredentials = true }) {}
}
However, the part of your 401-message that says "The target principal name is incorrect" looks suspicious. Your problem may arise from some issues in your AD-configuration rather than in the ServiceClient-configuration.
#bkwdesign has right
var credentials = new Microsoft.Rest.BasicAuthenticationCredentials();
var handler = new System.Net.Http.HttpClientHandler() { UseDefaultCredentials = true };
var uri = new Uri("http://your-rest-api:8008");
var svc = new WebApplication1Client(uri, credentials, handler);
//WebApplication1Client : ServiceClient<WebApplication1Client>, IWebApplication1Client
This is the way how to pass credentials from MVC to WebAPI Windows Authentication or impersonate credentials
Maybe other options:
var handler = new HttpClientHandler() { Credentials = CredentialCache.DefaultCredentials };
var handler = new HttpClientHandler() { Credentials = CredentialCache.DefaultNetworkCredentials };
I know that ServicePointManager.ServerCertificateValidationCallback no longer exists in .Net Core and is instead replaced with:
using(var handler = new System.Net.Http.HttpClientHandler())
{
using (var httpClient = new System.Net.Http.HttpClient(handler))
{
handler.ServerCertificateCustomValidationCallback = (request, cert, chain, errors) =>
{
return true;
};
}
}
However we are currently using the ServiceStack.Core library which, as far as I can see, does not expose either a property like this or the handler itself.
How would I tell a ServiceStack client to bypass ssl validation in this code?
using(var client = new JsonServiceClient("https://www.google.com"))
{
var response = client.Get("/results");
}
If there is a way, would this work the same on both Windows and Linux?
JsonServiceClient is built on .NET HttpWebRequest which has been rewritten in .NET Core as a wrapper over HttpClient so we'd generally recommend for .NET Core to avoid this overhead (which is much slower than .NET 4.5) and switch to using JsonHttpClient in ServiceStack.HttpClient instead as it uses HttpClient directly and where you can inject your own HttpClientHandler with:
var client = new JsonHttpClient(baseUrl)
{
HttpMessageHandler = new HttpClientHandler
{
UseCookies = true,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
ServerCertificateCustomValidationCallback = (req,cert,chain,errors) => true
}
};
Note it's recommended to reuse HttpClient instances so you should try to reuse HttpClient instances when possible and avoid disposing them.
I am working with Flurl to hit an API that requires certificate-based authentication. I have seen from this SO post that adding a certificate to a WebRequestHandler and instructing an HttpClient to use this handler is easy.
However, it is not so clear for me using Flurl. I have tried the follwing three things.
Extending DefaultHttpFactory
I first suspected that I needed to create my own X509HttpFactory : DefaultHttpFactory which would create the handler and assign it to the HttpClient. However, in viewing the source, I notice that the constructor for CreateClient already expects a handler. Where does this handler come from?
Creating Client using DefaultHttpFactory
WebRequestHandler handler = new WebRequestHandler();
handler.ClientCertificates.Add(myX509Cert);
var clientFactory = new DefaultHttpClientFactory();
FlurlClient fc = clientFactory.CreateClient(url, handler);
This does not compile as HttpClient cannot be casted to FlurlClient
Use ConfigureHttpClient
var clientFactory = new DefaultHttpClientFactory();
FlurlClient fc = new Url("foobar.com").ConfigureHttpClient(client => client = clientFactory
.CreateClient(url, handler));
This seems like the most viable option, but I'm unsure since the delegate is an Action with no return type.
The Question
What is the best/correct way to support client-side certificate authentication using Flurl?
You're close - a custom factory is definitely the way to go. But you want to override CreateMessageHandler rather than CreateClient:
public class X509HttpFactory : DefaultHttpClientFactory
{
private readonly X509Certificate2 _cert;
public X509HttpFactory(X509Certificate2 cert) {
_cert = cert;
}
public override HttpMessageHandler CreateMessageHandler() {
var handler = base.CreateMessageHandler();
handler.ClientCertificates.Add(_cert);
return handler;
}
}
Then you can either register it globally (on app startup):
FlurlHttp.Configure(settings => {
settings.HttpClientFactory = new X509HttpFactory(myCert);
});
Or for all calls to a particular host:
FlurlHttp.ConfigureClient(ROOT_URL, cli => {
cli.Settings.HttpClientFactory = new X509HttpFactory(myCert);
});
Or for a new FlurlClient:
var cli = new FlurlClient(url)
.Configure(settings => settings.HttpClientFactory = new X509HttpFactory(myCert));
Or an existing one:
cli.Settings.HttpClientFactory = new X509HttpFactory(myCert);
we have written a WinRT App connected to a Sharepoint 2013.
We are able to authenticate and login to the sharepoint, but we have problems with the logout 'process'. Login is implemented as follows:
We are setting up a HttpClient with the corresponding user credentials and domain information. The configuration is wrapped in the HttpClientConfig class an delivered to a the HttpClientService which holds the HttpClient object.
After that we retrieve the formdigestValue from the sharepoint and use the token in the X-RequestDigest Header in every request. If the token times out we retrieve a new one.
Here is some code how we implemented the above mentioned authentication.
public async Task Inialize()
{
var httpConfig = new HttpClientConfig();
httpConfig.Headers.Add("Accept", "application/json;odata=verbose");
httpConfig.Headers.Add("User-Agent", _userAgent);
httpConfig.DefaultTimeout = Statics.DEFAULT_NETWORK_TIMEOUT_SECONDS;
httpConfig.PreAuthenticate = true;
httpConfig.NetworkCredentials = new NetworkCredential(username, password, _domain);
_httpClientService.ResetCookies();
_httpClientService.ConfigureHttpClient(httpConfig);
}
The ConfigureHttpClient method disposes an old HttpClient instance and creates a new HttpClient instance, like this:
public void ConfigureHttpClient(HttpClientConfig config, bool disposeCurrent = true)
{
_config = config;
if (disposeCurrent)
{
DisposeHttpClient();
}
_httpClient = CreateHttpClient(config);
if (disposeCurrent)
{
//make sure remove old httpclient and httpclienthandler instances after they are not hold anywhere else
GC.Collect();
}
_httpClientDisposed = false;
}
public HttpClient CreateHttpClient(HttpClientConfig config)
{
_httpClientHandler = _httpClientFactoryService.CreateHttpClientHandler();
_httpClientHandler.CookieContainer = _cookieContainer;
_httpClientHandler.UseCookies = true;
_httpClientHandler.AllowAutoRedirect = config.AllowAutoRedirect;
_httpClientHandler.PreAuthenticate = config.PreAuthenticate;
if (config.NetworkCredentials != null)
{
_httpClientHandler.Credentials = config.NetworkCredentials;
}
var client = _httpClientFactoryService.CreateHttpClient(_httpClientHandler, true);
client.Timeout = TimeSpan.FromSeconds(config.DefaultTimeout);
if (config.UseGzipCompression)
{
if (_httpClientHandler.SupportsAutomaticDecompression)
{
_httpClientHandler.AutomaticDecompression = DecompressionMethods.GZip;
client.DefaultRequestHeaders.AcceptEncoding.Add(StringWithQualityHeaderValue.Parse("gzip"));
}
}
return client;
}
public void DisposeHttpClient()
{
var client = _httpClient;
_httpClientDisposed = true; //set flag before disposing is done to be able to react correctly!
if (client != null)
{
client.Dispose();
}
var handler = _httpClientHandler;
if (handler != null)
{
handler.Dispose();
}
GC.Collect();
}
public async Task<object> InitNewSharepointSession(bool useCookies = true)
{
var config = _httpClientService.CurrentClientConfig;
config.UseCookies = useCookies;
var res = await getRequestDigestAsync();
if (res.IsSuccess)
{
SharepointContextInformation = res.Response;
if (config.Headers.ContainsKey("X-RequestDigest"))
{
config.Headers.Remove("X-RequestDigest");
}
config.Headers.Add("X-RequestDigest", SharepointContextInformation.FormDigestValue);
return new DataServiceResponse<bool>(true);
}
else
{
return new DataServiceResponse<bool>(res.Error);
}
}
The ResetCookies method only disposes the old cookies list:
public void ResetCookies()
{
_cookieContainer = new CookieContainer();
}
As you can see we used some GC.Collect() calls which shows a bit our helplessness according the logout stuff.
For logout, we just dispose our httpclient.
But for some reason, if we login with another user, we sometimes get the data of the previous user which is a pretty high rated bug for us.
Everything works nice if we restart the app, but if we only dispose the current users httpClient we may run in this failure having access with the wrong credential/user context of the previous user.
Another thing I watched is the behaviour after a password change. The old password remains and is valid until the app has been restarted.
So I would be very thankful for some hints or suggestions of a sharepoint REST specialist on how to solve this issue.
I guess you are creating a Universal app for Windows 10. In that case, there is no other option than restarting the app, see this answer.
HTTP credentials are not the same as cookies, so resetting the cookies will not help.
However, if you are using System.Net.Http.HttpClient in a Windows 8/8.1 project (no Universal project), disposing the HttpClient should work.
Example with Windows 8/8.1 template. Do NOT use with Universal template.
private async void Foo()
{
// Succeeds, correct username and password.
await Foo("foo", "bar");
// Fails, wrong username and passord.
await Foo("fizz", "buzz");
}
private async Task Foo(string user, string password)
{
Uri uri = new Uri("http://heyhttp.org/?basic=1&user=foo&password=bar");
HttpClientHandler handler = new HttpClientHandler();
handler.Credentials = new System.Net.NetworkCredential(user, password);
HttpClient client = new HttpClient(handler);
Debug.WriteLine(await client.GetAsync(uri));
}
I have a web application (hosted in IIS) that talks to a Windows service. The Windows service is using the ASP.Net MVC Web API (self-hosted), and so can be communicated with over http using JSON. The web application is configured to do impersonation, the idea being that the user who makes the request to the web application should be the user that the web application uses to make the request to the service. The structure looks like this:
(The user highlighted in red is the user being referred to in the examples below.)
The web application makes requests to the Windows service using an HttpClient:
var httpClient = new HttpClient(new HttpClientHandler()
{
UseDefaultCredentials = true
});
httpClient.GetStringAsync("http://localhost/some/endpoint/");
This makes the request to the Windows service, but does not pass the credentials over correctly (the service reports the user as IIS APPPOOL\ASP.NET 4.0). This is not what I want to happen.
If I change the above code to use a WebClient instead, the credentials of the user are passed correctly:
WebClient c = new WebClient
{
UseDefaultCredentials = true
};
c.DownloadStringAsync(new Uri("http://localhost/some/endpoint/"));
With the above code, the service reports the user as the user who made the request to the web application.
What am I doing wrong with the HttpClient implementation that is causing it to not pass the credentials correctly (or is it a bug with the HttpClient)?
The reason I want to use the HttpClient is that it has an async API that works well with Tasks, whereas the WebClient's asyc API needs to be handled with events.
You can configure HttpClient to automatically pass credentials like this:
var myClient = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
I was also having this same problem. I developed a synchronous solution thanks to the research done by #tpeczek in the following SO article: Unable to authenticate to ASP.NET Web Api service with HttpClient
My solution uses a WebClient, which as you correctly noted passes the credentials without issue. The reason HttpClient doesn't work is because of Windows security disabling the ability to create new threads under an impersonated account (see SO article above.) HttpClient creates new threads via the Task Factory thus causing the error. WebClient on the other hand, runs synchronously on the same thread thereby bypassing the rule and forwarding its credentials.
Although the code works, the downside is that it will not work async.
var wi = (System.Security.Principal.WindowsIdentity)HttpContext.Current.User.Identity;
var wic = wi.Impersonate();
try
{
var data = JsonConvert.SerializeObject(new
{
Property1 = 1,
Property2 = "blah"
});
using (var client = new WebClient { UseDefaultCredentials = true })
{
client.Headers.Add(HttpRequestHeader.ContentType, "application/json; charset=utf-8");
client.UploadData("http://url/api/controller", "POST", Encoding.UTF8.GetBytes(data));
}
}
catch (Exception exc)
{
// handle exception
}
finally
{
wic.Undo();
}
Note: Requires NuGet package: Newtonsoft.Json, which is the same JSON serializer WebAPI uses.
What you are trying to do is get NTLM to forward the identity on to the next server, which it cannot do - it can only do impersonation which only gives you access to local resources. It won't let you cross a machine boundary. Kerberos authentication supports delegation (what you need) by using tickets, and the ticket can be forwarded on when all servers and applications in the chain are correctly configured and Kerberos is set up correctly on the domain.
So, in short you need to switch from using NTLM to Kerberos.
For more on Windows Authentication options available to you and how they work start at:
http://msdn.microsoft.com/en-us/library/ff647076.aspx
OK, so thanks to all of the contributors above. I am using .NET 4.6 and we also had the same issue. I spent time debugging System.Net.Http, specifically the HttpClientHandler, and found the following:
if (ExecutionContext.IsFlowSuppressed())
{
IWebProxy webProxy = (IWebProxy) null;
if (this.useProxy)
webProxy = this.proxy ?? WebRequest.DefaultWebProxy;
if (this.UseDefaultCredentials || this.Credentials != null || webProxy != null && webProxy.Credentials != null)
this.SafeCaptureIdenity(state);
}
So after assessing that the ExecutionContext.IsFlowSuppressed() might have been the culprit, I wrapped our Impersonation code as follows:
using (((WindowsIdentity)ExecutionContext.Current.Identity).Impersonate())
using (System.Threading.ExecutionContext.SuppressFlow())
{
// HttpClient code goes here!
}
The code inside of SafeCaptureIdenity (not my spelling mistake), grabs WindowsIdentity.Current() which is our impersonated identity. This is being picked up because we are now suppressing flow. Because of the using/dispose this is reset after invocation.
It now seems to work for us, phew!
In .NET Core, I managed to get a System.Net.Http.HttpClient with UseDefaultCredentials = true to pass through the authenticated user's Windows credentials to a back end service by using WindowsIdentity.RunImpersonated.
HttpClient client = new HttpClient(new HttpClientHandler { UseDefaultCredentials = true } );
HttpResponseMessage response = null;
if (identity is WindowsIdentity windowsIdentity)
{
await WindowsIdentity.RunImpersonated(windowsIdentity.AccessToken, async () =>
{
var request = new HttpRequestMessage(HttpMethod.Get, url)
response = await client.SendAsync(request);
});
}
It worked for me after I set up a user with internet access in the Windows service.
In my code:
HttpClientHandler handler = new HttpClientHandler();
handler.Proxy = System.Net.WebRequest.DefaultWebProxy;
handler.Proxy.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
.....
HttpClient httpClient = new HttpClient(handler)
....
Ok so I took Joshoun code and made it generic. I am not sure if I should implement singleton pattern on SynchronousPost class. Maybe someone more knowledgeble can help.
Implementation
//I assume you have your own concrete type. In my case I have am using code first with a class called FileCategory
FileCategory x = new FileCategory { CategoryName = "Some Bs"};
SynchronousPost<FileCategory>test= new SynchronousPost<FileCategory>();
test.PostEntity(x, "/api/ApiFileCategories");
Generic Class here. You can pass any type
public class SynchronousPost<T>where T :class
{
public SynchronousPost()
{
Client = new WebClient { UseDefaultCredentials = true };
}
public void PostEntity(T PostThis,string ApiControllerName)//The ApiController name should be "/api/MyName/"
{
//this just determines the root url.
Client.BaseAddress = string.Format(
(
System.Web.HttpContext.Current.Request.Url.Port != 80) ? "{0}://{1}:{2}" : "{0}://{1}",
System.Web.HttpContext.Current.Request.Url.Scheme,
System.Web.HttpContext.Current.Request.Url.Host,
System.Web.HttpContext.Current.Request.Url.Port
);
Client.Headers.Add(HttpRequestHeader.ContentType, "application/json;charset=utf-8");
Client.UploadData(
ApiControllerName, "Post",
Encoding.UTF8.GetBytes
(
JsonConvert.SerializeObject(PostThis)
)
);
}
private WebClient Client { get; set; }
}
My Api classs looks like this, if you are curious
public class ApiFileCategoriesController : ApiBaseController
{
public ApiFileCategoriesController(IMshIntranetUnitOfWork unitOfWork)
{
UnitOfWork = unitOfWork;
}
public IEnumerable<FileCategory> GetFiles()
{
return UnitOfWork.FileCategories.GetAll().OrderBy(x=>x.CategoryName);
}
public FileCategory GetFile(int id)
{
return UnitOfWork.FileCategories.GetById(id);
}
//Post api/ApileFileCategories
public HttpResponseMessage Post(FileCategory fileCategory)
{
UnitOfWork.FileCategories.Add(fileCategory);
UnitOfWork.Commit();
return new HttpResponseMessage();
}
}
I am using ninject, and repo pattern with unit of work. Anyways, the generic class above really helps.
Set identity's impersonation to true and validateIntegratedModeConfiguration to false in web.config
<configuration>
<system.web>
<authentication mode="Windows" />
<authorization>
<deny users="?" />
</authorization>
<identity impersonate="true"/>
</system.web>
<system.webServer>
<validation validateIntegratedModeConfiguration="false" ></validation>
</system.webServer>
</configuration>
string url = "https://www..com";
System.Windows.Forms.WebBrowser webBrowser = new System.Windows.Forms.WebBrowser();
this.Controls.Add(webBrowser);
webBrowser.ScriptErrorsSuppressed = true;
webBrowser.Navigate(new Uri(url));
var webRequest = WebRequest.Create(url);
webRequest.Headers["Authorization"] = "Basic" + Convert.ToBase64String(Encoding.Default.GetBytes(Program.username + ";" + Program.password));
webRequest.Method = "POST";