I'm trying to communicate with a self-hosted WebAPI client using HttpClient. The client is created with the following code:
HttpClientHandler clientHandler = new HttpClientHandler()
{
UseDefaultCredentials = true,
PreAuthenticate = true
};
var client = new HttpClient(clientHandler);
on the server side we set:
HttpListener listener = (HttpListener)app.Properties[typeof(HttpListener).FullName];
listener.AuthenticationSchemes = AuthenticationSchemes.IntegratedWindowsAuthentication;
in the Startup file.
The problem is that I get two (or one after preauthentication) 401 errors before the request is processed.
In fiddler the sequence looks like this:
First request:
Authorization: Negotiate TlRMTVNTUAABAAAAl7II4gcABwAxAAAACQAJACgAAAAGAbEdAAAAD1dTMTEzLTEyMFNXVC0xMTM=
Answer:
WWW-Authenticate: Negotiate TlRMTVNTUAACAAAADgAOADgAAAAVwonisrQOBMTKHhKwCkgCAAAAAJoAmgBGAAAABgGxHQAAAA9TAFcAVAAtADEAMQAzAAIADgBTAFcAVAAtADEAMQAzAAEAEgBXAFMAMQAxADMALQAxADIAMAAEABYAcwB3AHQALQAxADEAMwAuAGwAbwBjAAMAKgBXAFMAMQAxADMALQAxADIAMAAuAHMAdwB0AC0AMQAxADMALgBsAG8AYwAFABYAQQBMAEQASQA5ADkAOQAuAGwAbwBjAAcACACkGh0XVY3QAQAAAAA=
Second request (succeeds):
Authorization: Negotiate TlRMTVNTUAADAAAAAAAAAFgAAAAAAAAAWAAAAAAAAABYAAAAAAAAAFgAAAAAAAAAWAAAAAAAAABYAAAAFcKI4gYBsR0AAAAPfJafWSuLL0sAXYtWCynOqg==
So why isn't my client sending the correct authorization token the first time around but always needs this two-time approach?
What you are experiencing is normal, this is how the NTLM authentication scheme works.
1: C --> S GET ...
2: C <-- S 401 Unauthorized
WWW-Authenticate: NTLM
3: C --> S GET ...
Authorization: NTLM <base64-encoded type-1-message>
4: C <-- S 401 Unauthorized
WWW-Authenticate: NTLM <base64-encoded type-2-message>
5: C --> S GET ...
Authorization: NTLM <base64-encoded type-3-message>
6: C <-- S 200 Ok
The client sends a GET request to the server.
Since you need to be authenticated to access the requested resource, the server sends back a 401 Unathorized response and notifies the client in the WWW-Authenticate header that it supports NTLM authentication. So this is where you get your first 401 response code.
The client sends the domain name and the username to the server in the Authorization header. Note that based solely on these information the client cannot be authenticated yet.
The server sends a challenge to the client. It's a randomly generated number called a nonce. This is where you get your second 401 response code.
The client sends back a response to the server's challenge, using its password's hash to encrypt the random number.
The server sends the client's username, the challenge sent to the client and the response received from the client to the domain controller. Using the username the domain controller retrieves the hash of the user's password and encrypts the challenge with it. If the result matches the response sent by the client, the client is authenticated and the server sends back a 200 response code and the requested resource to the client.
Faced a similar issue, and after going through a lot of answers, where none of those worked. The following worked, and it doesn't come up with two 401's:
var credential = new NetworkCredential("username", "password", "domainname");
var myCache = new CredentialCache();
// Add the target Uri to the CredentialCache with credential object
myCache.Add(new Uri("http://targeturi/"), "NTLM", credential);
// Create an HttpClientHandler to add some settings
var handler = new HttpClientHandler();
handler.AllowAutoRedirect = true;
handler.Credentials = myCache;
// Create an HttpClient with the handler object
httpClient = new HttpClient(handler);
// Wait to get the reponse, and you can use the reponse in your code
HttpResponseMessage response = await httpClient.GetAsync(resourceUri);
Source
the default behavior where it only sends credentials after receiving an HTTP 401 Not Authorized response.
Manually adding the credentials header seems to be the best solution available.
More details in this post
Related
I am trying to consume oauth2 api using auth code. In first step, i received auth by providing client id and secret, and now in 2nd step, i need access token using that auth code.
I tried below c# code
var client1 = new RestClient("https://ant.aliceblueonline.com/oauth2/token");
var request1 = new RestRequest(Method.POST);
request1.AddHeader("Content-Type", "application/x-www-form-urlencoded");
request1.AddParameter("code", "xxxxxxxxxxxxxxxxx");
request1.AddParameter("grant_type", "authorization_code");
request1.AddParameter("redirect_uri", "https://ant.aliceblueonline.com/plugin/callback");
request1.AddParameter("client_id", "MM01418");
request1.AddParameter("client_secret", "xxxxxxxxxxxxxx");
IRestResponse response = client1.Execute(request1);
In response, I get
The OAuth 2.0 Client supports client authentication method "client_secret_basic", but method "client_secret_post" was requested. You must configure the OAuth 2.0 client's "token_endpoint_auth_method" value to accept "client_secret_post
Tried a lot but could not resolve it.
First of all as you're working with C# I would like to recommend you to use IdentityModel to interact with any OAuth2 authorization server or OpenId Connect Provider.
Let's start by the definition of a Client: A client is an application that is allowed to request acces tokens on behalf of the user. In your example your server runing the code you posted is the client.
To be able to use the token endpoint to request a new access_token a client must be able to prove its identity first, by providing a client_id and client_secret (like a user and password for clients).
There are two methods for providing this client credentials, from IdentityServer4 documentation about secrets:
Authentication using a shared secret
You can either send the client id/secret combination as part of the POST body:
POST /connect/token
client_id=client1& client_secret=secret& ...
..or as a basic authentication header:
POST /connect/token
Authorization: Basic xxxxx
In this case, the error response is saying that just one of those methods is allowed, which is Authentication header
So instead of passing your client_id and client_secret along your request body:
request1.AddParameter("client_id", "MM01418");
request1.AddParameter("client_secret", "xxxxxxxxxxxxxx");
you need to concat client_id and client_secret with a collon as separator like "MM01418:xxxxxxxxxxxxxxxx" and apply base64 codification. Then add it to your request as header, of the form Authorization: Basic TU0wMTQxODp4eHh4eHh4eHh4eHh4eHh4. You can do this in c# by using the following code:
var credentials = string.Format("{0}:{1}", clientId, clientSecret);
var headerValue = Convert.ToBase64String(Encoding.UTF8.GetBytes(credentials));
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", headerValue);
or instead leave this low level details to a library like IdentityModel Nuget Docs that implements the correct requests for you depending on the case.
I have developed a C# desktop application which makes HTTPS requests to the customers' servers (usually Documentum/SharePoint/Alfresco/NemakiWare/etc HTTPS-based servers).
Several customers have asked us to support their servers which are protected by CA SSO (new name of Siteminder).
QUESTION: What do I need to do to allow my application to send HTTPS requests (and receive responses) with CA SSO-protected servers?
I have developed NTLM-SSO support for our C# desktop application and it works well, but I am not sure about how to proceed for CA SSO.
I have asked the same question on the CA forum, but like most questions there it remains unanswered.
To authenticate with CA SSO and then connect to the desired URL we need to access a protected resource on a web server configured to use CA SSO authentication:
Requests a resource on the server, using an HTTP request.
The request is received by the web server and is intercepted by the CA SSO web agent.
The web agent determines whether or not the resource is protected, and if so, gathers the user’s credentials and passes them to the Policy server.
The Policy server authenticates the user and verifies whether or not the authenticated user is authorized for the requested resource, based on rules and policies contained in the Policy store.
After the user is authenticated and authorized, the Policy server grants access to the protected resources.
This is accomplished with the following steps:
Open a connection (HTTP request in this case) to the URI of the protected resource. Since the request has not yet been authenticated, the CA SSO agent will issue a redirect to a login page. In the code, AllowAutoRedirect is set to false. This is important as the redirect URL will be required for the subsequent POST of login data in step 3 below. If AllowAutoRedirect were True, the response would not include a Location header and the subsequent POST would be made to the original URL, which would then redirect to the login page again. However, a POST occurs between a client and the server, any POST data carried in the payload of the request of step 3 will be lost during the redirect.
Dim request As HttpWebRequest
Dim response As HttpWebResponse
Dim url As String = PROTECTED_URL
request = WebRequest.Create(url)
request.AllowAutoRedirect = False
response = request.GetResponse
' make sure we have a valid response
If response.StatusCode <> HttpStatusCode.Found Then
Throw New InvalidProgramException
End If
' get the login page
url = response.Headers("Location")
request = WebRequest.Create(url)
request.AllowAutoRedirect = False
response = request.GetResponse
The next step involves creating an HTTPS request that POSTs all the form data, including userid and password, back to the server. The purpose of an authentication agent is to verify a user’s identity by validating their userid and password. Thus, their URLs naturally use SSL (secure sockets layer) and are encrypted for us, so we do not required further encryption in our program. However, the formatting of the POST data is interesting in as much as there are two alternatives. The sample program uses the simpler approach of setting the content type to application/x-www-form-urlencoded. Here the POST data is formatted similar to a query string and sent as part of the next request.
Dim postData As String
postData = ""
For Each inputName As String In tags.Keys
If inputName.Substring(0, 2).ToLower = "sm" Then
postData &= inputName & "=" & _
HttpUtility.UrlEncode(tags(inputName)) & "&"
End If
Next
postData += "postpreservationdata=&"
postData += "USER=" + HttpUtility.UrlEncode(USERNAME) & "&"
postData += "PASSWORD=" + HttpUtility.UrlEncode(PASSWORD)
request = WebRequest.Create(url)
cookies = New CookieContainer
request.CookieContainer = cookies
request.ContentType = FORM_CONTENT_TYPE
request.ContentLength = postData.Length
request.Method = POST_METHOD
request.AllowAutoRedirect = False ' Important
Dim sw As StreamWriter = New StreamWriter(request.GetRequestStream())
sw.Write(postData)
sw.Flush()
sw.Close()
response = request.GetResponse
Same idea as Mohit's answer, but it can be done with a much simpler code:
//Make initial request for SM to give you some cookies and the authentication URI
RestClient client = new RestClient("http://theResourceDomain/myApp");
client.CookieContainer = new CookieContainer();
IRestResponse response = client.Get(new RestRequest("someProduct/orders"));
//Now add credentials.
client.Authenticator = new HttpBasicAuthenticator("username", "password");
//Get resource from the SiteMinder URI which will redirect back to the API URI upon authentication.
response = client.Get(new RestRequest(response.ResponseUri));
Although this uses RestSharp, it can be easily replicated using HttpClient or even HttpWebRequest.
I have rather strange problem. With all that questions over internet how to add and get cookies, I want the opposite ;)
When I try to send request via HttpHandler it is adding it's own Cookie header. I have to get rid of it. Without digging into details - when it is added, server I am trying to request is giving me wrong answer. It works without this cookie (tried in fiddler).
But back to the problem, code:
string domain = "someMysteriousDomain";
var handler = new HttpClientHandler();
handler.UseDefaultCredentials = false;
handler.AllowAutoRedirect = true;
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.UseCookies = false;
var httpClient = new HttpClient(handler);
var request = new HttpRequestMessage(HttpMethod.Get, domain);
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("Mozilla", "5.0"));
request.Headers.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
var response = await httpClient.SendAsync(request);
Raw request seen in fiddler:
GET https://domain HTTP/1.1
Accept-Encoding: gzip
User-Agent: Mozilla/5.0
Host: domain
Connection: Keep-Alive
Cookie: cadata477E7C1824F44800AF0077724F65345="51595d316-0286-44bb-bc6f-ffb1fd311a92SqJA36rA69YW7aBg+iHXYi9LAcBLN6DBWE8a3MLejd2VCluO/UQ5eF6F6T4NWh4NhdRcv4rea15Hs0e2q6GatMac59UVbljhREYdH6PRbzZC/2qn8QHtpc6go5B56R"; mobile=0
I don't want to add that cookie! How to delete/clear/whatever it?
I am using Visual Studio Community 2015, with Windows Universal Project.
What is interesting, after rebooting my pc after few hours, I was able to make 2 or 3 requests without this cookie (using THE SAME code) and then mysterios cookie returned.
What it is about? How to get rid of it?
Thank you for reporting this issue - this is a known issue with the System.Net.Http.HttpClientHandler API implementation on Windows 10 and we are working on fixing it in an upcoming release.
In the meanwhile, a possible workaround is to use the Windows.Web.Http.HttpClient API with the underlying HttpBaseProtocolFilter class. This class has a property called CookieManager that stores all the cookies for each URI. You can write a method to delete the cookies from the CookieManager for the destination URI before sending a request. This will ensure that no cookies get sent. You can see this sample for how to delete cookies from the CookieManager: https://github.com/Microsoft/Windows-universal-samples/tree/master/httpclient
Thanks
Sidharth [MSFT]
My app communicates with an internal web API that requires authentication.
When I send the request I get the 401 challenge as expected, the handshake occurs, the authenticated request is re-sent and everything continues fine.
However, I know that the auth is required. Why do I have to wait for the challenge? Can I force the request to send the credentials in the first request?
My request generation is like this:
private static HttpWebRequest BuildRequest(string url, string methodType)
{
var request = HttpWebRequest.CreateHttp(url);
request.PreAuthenticate = true;
request.AuthenticationLevel = AuthenticationLevel.MutualAuthRequested;
request.Credentials = CredentialCache.DefaultNetworkCredentials;
request.Proxy.Credentials = CredentialCache.DefaultNetworkCredentials;
request.ContentType = CONTENT_TYPE;
request.Method = methodType;
request.UserAgent = BuildUserAgent();
return request;
}
Even with this code, the auth header isn't included in the initial request.
I know how to include the auth info with basic.... what I want to do is to use Windows Auth of the user executing the app (so I can't store the password in a config file).
UPDATE I also tried using a HttpClient and its own .Credentials property with the same result: no auth header is added to the initial request.
The only way I could get the auth header in was to hack it in manually using basic authentication (which won't fly for this use-case)
Ntlm is a challenge/response based authentication protocol. You need to make the first request so that the server can issue the challenge then in the subsequent request the client sends the response to the challenge. The server then verifies this response with the domain controller by giving it the challenge and the response that the client sent.
Without knowing the challenge you can't send the response which is why 2 requests are needed.
Basic authentication is password based so you can short circuit this by sending the credentials with the first request but in my experience this can be a problem for some servers to handle.
More details available here:
http://msdn.microsoft.com/en-us/library/windows/desktop/aa378749(v=vs.85).aspx
I'm not 100% sure, but I suspect that there is no way around this; it's simply the way HttpWebRequest works.
In the online .NET source, function DoSubmitRequestProcessing which is here, you can see this comment just after the start of the function, line 1731:
// We have a response of some sort, see if we need to resubmit
// it do to authentication, redirection or something
// else, then handle clearing out state and draining out old response.
A little further down (line 1795) (some lines removed for brevity)
if (resubmit)
{
if (CacheProtocol != null && _HttpResponse != null) CacheProtocol.Reset();
ClearRequestForResubmit(ntlmFollowupRequest);
...
}
And in ClearRequestForResubmit line 5891:
// We're uploading and need to resubmit for Authentication or Redirect.
and then (Line 5923):
// The second NTLM request is required to use the same connection, don't close it
if (ntlmFollowupRequest) {....}
To my (admittedly n00bish) eyes these comments seem to indicate that the developers decided to follow the "standard" challenge-response protocol for NTML/Kerberos and not include any way of sending authentication headers up-front.
Setting PreAuthenticate is what you want, which you are doing. The very first request will still do the handshake but for subsequent requests it will automatically send the credentials (based on the URL being used). You can read up on it here: http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.preauthenticate(v=vs.110).aspx.
I have a office plugin that connect a service using HttpWebRequest.
Inside a domain I pass CredentialCache.DefaultNetworkCredentials so all is fine.
Outside a domain a user need to provide username, domain and password.
This don't work atm.
Some part of the code out of it:
CookieContainer cookies = new CookieContainer();
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = WebRequestMethods.Http.Post;
request.AllowAutoRedirect = true;
request.CookieContainer = cookies; // provide session cookie to handle redirects of login controller of the webservice
if (isWindowAuthentication) // isWindowAuthentication is set earlier by config
{
if (Common.UserName.Length > 0)
{
string[] domainuser;
string username;
string domain;
if (Common.UserName.Contains("#"))
{
domainuser = Common.UserName.Split('#');
username = domainuser[0];
domain = domainuser[1];
}
else
{
domainuser = Common.UserName.Split('\\');
username = domainuser[1];
domain = domainuser[0];
}
NetworkCredential nc = new NetworkCredential(username, Common.Password, domain);
CredentialCache cache = new CredentialCache();
cache.Add(request.RequestUri, "NTLM", nc);
request.Credentials = cache;
}
else
{
request.Credentials = CredentialCache.DefaultNetworkCredentials;
}
}
Later on I do the request request.GetResponse();.
If I use CredentialCache.DefaultNetworkCredentials then everything works fine.
The moment I switch to my own new NetworkCredential() part the authentication fails.
I checked the logs of the Apache (it is Apache 2.2 using SSPI mod).
When it succeed the first request redirect to the login controller, then the login controller request credentials. Passed and works (redirect to the target site).
Log 1 (works):
192.168.14.9 - - [25/Oct/2012:11:35:35 +0200] "POST /ror/ioi/start?document%5Bguid%5D=%7Be3d8f1de-10f2-4493-a0c0-97c2acb034e6%7D HTTP/1.1" 302 202
192.168.14.9 - - [25/Oct/2012:11:35:35 +0200] "GET /ror_auth/login?ror_referer=%2Fror%2Fioi%2Fstart%3Fdocument%255Bguid%255D%3D%257Be3d8f1de-10f2-4493-a0c0-97c2acb034e6%257D HTTP/1.1" 401 401
192.168.14.9 - - [25/Oct/2012:11:35:35 +0200] "GET /ror_auth/login?ror_referer=%2Fror%2Fioi%2Fstart%3Fdocument%255Bguid%255D%3D%257Be3d8f1de-10f2-4493-a0c0-97c2acb034e6%257D HTTP/1.1" 401 401
192.168.14.9 - rausch [25/Oct/2012:11:35:35 +0200] "GET /ror_auth/login?ror_referer=%2Fror%2Fioi%2Fstart%3Fdocument%255Bguid%255D%3D%257Be3d8f1de-10f2-4493-a0c0-97c2acb034e6%257D HTTP/1.1" 302 156
The own credential results here Log 2 (do not work):
192.168.14.9 - - [25/Oct/2012:12:05:23 +0200] "POST /ror/ioi/start?document%5Bguid%5D=%7B6ac54e8a-19f1-4ccd-9684-8d864dd9ccf7%7D HTTP/1.1" 302 202
192.168.14.9 - - [25/Oct/2012:12:05:23 +0200] "GET /ror_auth/login?ror_referer=%2Fror%2Fioi%2Fstart%3Fdocument%255Bguid%255D%3D%257B6ac54e8a-19f1-4ccd-9684-8d864dd9ccf7%257D HTTP/1.1" 401 401
What I don't understand is when I inspect e.g. CredentialCache.DefaultNetworkCredentials.UserName then is is empty.
Anyone know what to do and how I have to set my own credentials correct that the authentication works as expected?
Finally after a lot of testing and investigation and many resources on stack overflow I found out what is going on.
The problem seems to be that the httpwebrequest don't handle the authentication when parts of the webseite requests credentials and some don't.
Background:
Our Site has its own session management and redirect to a login controller when no valid session is available. Only this login controller is set to NTLM authentication.
This we made because we have a web site without NTLM auth at all (no 401, 302 request loops in IE!) and only validate once (and we handle authentication on different url to prevent the problem that IE stop posting data at non-authenticated sites => see http://support.microsoft.com/?id=251404).
Solution:
I normally sent a request on my target page and the webserver redirect, authenticate and redirect back to the target. As the httpwebrequest don't handle this for any reason if I have my own credentials set (see above code of my question) I changed to code to authenticate once to my login controller and store the session in a cookie container.
For all following request I don't autenticate at all anymore. I add the cookie container and my server gets a valid session. So I don't have to authenticate anymore. Sideeffect is better performance this way.
Another tricky thing was that I not only use httpwebrequest, I also use a webform control.
Therefor I found the solution to add my own cookie session here: Use cookies from CookieContainer in WebBrowser (Thanks to Aaron who saved me a lot of trouble as well).