Credentials on HttpClient is not validated after first successful REST call - c#

I'm creating an application where the user is logging in with a Username, Password and a Domain. I want to make as much as it is reusable across Windows platforms so I'm using the nuget package Microsoft HTTP Client libraries in a Portable Class Library.
Here is how i create the HttpClient with a HttpClientHandler and then calling the GetAsync.
HttpClientHandler handler = new HttpClientHandler();
ICredentials myCredentials = new NetworkCredential("Username", "Password", "Domain");
handler.Credentials = myCredentials;
HttpClient client = new HttpClient(handler);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.BaseAddress = new Uri("https://....");
HttpResponseMessage response = await client.GetAsync("...");
This seems to work fine. The credentials are send in the request and only registered users are allowed to get the data.
In my application the users also have the option to sign out and then sign in again with possibly another username, password or domain. And here is where the problem is.
If I have called the client.GetAsync with some valid credentials one time, the HttpClient seems to remember the old user credentials, although I'm creating a new instance of HttpClient each time and setting the correct credentials for the new user.
So my questions is, Is the HttpClient keeping a network channel open or is there some session problem that I'm not aware of?
--- Update #1 ---
If I make the URLs unique in GetAsync(...), e.g. I could pass some random parameter with the request, the server will validate the credentials and only Authorized users will get access to the resource. It is not really a good solution, so I did some more research.
I looks like the server is sending a response header called Persistent-Auth: true. This tells the client that the Authorization header is not required for the next request. I geuss thats why the credentials are not sent the next I try to call the GetAsync for the same resource. Surprisingly I also noticed in Fiddler that for the second request to this resource, no HTTP request is being sent at all from the client.
One interesting thing is that if I try the same approach in a browser, the Authorization has the same behavior, so its only included in the first request. For the second request to the same resource, I can see in Fiddler that a HTTP request is being sent as you would expect.
So to sum it all. I guess I'm stuck with 2 issues. First, is it possible to change this Persistent-Auth behavior so it is set to false in the server response. Second, why is my application not sending any request at all the second time I'm requesting the same resource.

According to the answer of this question:
How to stop credential caching on Windows.Web.Http.HttpClient?
It should work for Windows build 10586 onwards.
To manual clear all cached credentials, we can also call the method HttpBaseProtocolFilter.ClearAuthenticationCache() which clears all cached credential information. Documentation for this method can be found here: https://learn.microsoft.com/en-us/uwp/api/Windows.Web.Http.Filters.HttpBaseProtocolFilter

Related

C# HttpClient POST requests from Azure Function with Authorization tag intended for third-party API are stripped of Headers and Body

UPDATE
I was able to get a working request posted. The third-party API has us sending the Token (which is basically a Guid) as a bearer token. Azure appears to do some sort of pre-validation on this. When I swapped out the GUID with a true randomly generated bearer token, it worked.
I do still wonder if there's a way to disable this check-in Azure. The "bad" Bearer token works for GET requests but fails for POST/PUT requests.
Summary of the Application
We have Azure Functions (i.e., Time Trigger, Orchestrator, Activities) that look for items in an on-prem queue table in SQL and then POST it to a third-party API via JSON.
The third-party API requires an Authorization header with the POST request.
Technical Overview
dotnet core 3.1
azure function runtime ~3
Additional Information
This codebase worked fine during UAT back in April-May of this year. It then sat idle until we rebooted the project a couple of weeks ago.
Outbound requests are not proxied through APIM. They're sent directly to the third-party API
Application Insights is configured for the Azure Function
What works
All of the GET requests. No issues at all.
What doesn't work
POST requests. I proxied the requests to a beeceptor to see exactly what was being received. When the Authorization header is included most of the headers are stripped (I.e., Content-Type, Content-Length) and the Body of the request is blank.
If I removed the Authorization header then all headers and body are received as expected.
Question
I can only assume at this point that some Azure service, pre-flight check, security policy is intercepting the Authorization header thinking it's intended for "itself", but I have absolutely no idea what it could be. I've been on Google now for days.
Simplified Version of Code
using var client = new HttpClient();
client.DefaultRequestHeaders.Clear();
// Request params are dynamic and a helper method builds the full request path
var path = PathBuilder(queueItem.RequestParams, queueItem.Request.UrlPath);
// This can change in code not shown if the request is sending files
var contentType = "application/json";
client.BaseAddress = new Uri(queueItem.Request.Client.BaseApiUrl);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue { NoCache = true };
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", queueItem.Request.Client.AuthToken);
// queueItem.Data is JSON
HttpContent json = new StringContent(queueItem.Data, Encoding.UTF8, contentType);
return await client.PostAsync(path, json);
Also...
I've confirmed the JSON body is valid
The code did work and has remain unchanged
Given all that you’ve tried, it might be a long shot, but have you tried to add the token like:
client.DefaultRequestHeaders.TryAddWithoutValidation(“Authorization”, “bearer token here…”);
and then check whether the try succeeded or not?

Postman omits original Oauth2 authentication handshake code

I have a Postman request sent by a partner/client that requires Oauth2 to hit their web service endpoint. Here is the authorization section:
I need to re-create this request in C#. Should be easy, just click on Code link, and grab the sample code in C# - RestSharp. Two problems:
1- When I execute this in Postman, I get The SAML2 token is not valid because its validity period has ended. So then I click the Get New Access Token button, and I get challenged for credentials:
(With the Client ID and Client Secret, shouldn't I be able to bypass this challenge?)
2- Ultimately I need to be able to run this request in my c# app. When I click the Code link in Postman it gives a nice C# example, but the problem with the sample code generated by Postman is that it assumes the bearer token has already been obtained, and just plops that into the source sample. But the obtaining of the bearer token is a very important piece that Postman omits. Here is my specific example, copied from Postman:
var client = new RestClient("https://myurl-here:7148/foo/ODataV4/WS3_stuff?Company='abc123'");
client.Timeout = -1;
var request = new RestRequest(Method.POST);
request.AddHeader("Content-Type", "application/json");
request.AddHeader("Authorization", "Bearer ey...massive-string...Zb");
request.AddHeader("Cookie", "ApplicationGatewayAffinity=ab721.more.d6c1a341bc; ApplicationGatewayAffinityCORS=ab..more...bc");
request.AddParameter("application/json", "{a-bunch-of-json-here}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
What are the pieces I'm missing? How can I get the C# equivalent of whatever Postman is doing to obtain the bearer and those Cookie values? And I thought there's a way for applications to achieve all the Oauth handshaking without sending the user to that MS window as in screenshot above - but how is it done?
ClientID and ClientSecret are there to identify your application not the user. They are sent to the Authorization Server so that the server knows that it can issue access tokens to this application. User authentication is another thing. You will always have to authenticate the user in order to get an access token which allows to access that user's data.
Unless you only need to authenticate your application and want to access data which does not belong to any user. In that case you need a client credentials flow, an OAuth flow which allows your application to get an access token.
Postman is a tool for making calls to APIs, it just generates the code which enables you to make the API call. Have a look at some C# OAuth clients (or maybe something for your framework, if you're using one). Those clients will enable you to easily generate new access tokens. Still, if you need a user's access token remember that you will need your users to open a browser. If you're developing a backend app, which does not serve any pages then you can have a look at the OAuth device flow, which enables you to authenticate users on a different device than your app runs.
As for the cookies - make sure whether you really need them. If you're calling an API chances are that those cookies are not required to make the request.

DocuSign JWT Impersonation Issues

I'm working through the JWT impersonation flows documented here and here. I'm using C#, and though I have worked through a few of the quick start applications, I'm still having some issues.
Existing Flow
The flow I have so far, which seems to be functional in DS sandbox/dev/demo, is:
Send user to DocuSign (oauth/auth). scope is "signature impersonation". (I've tried it with a bunch more permissions thrown in as well.)
After DS auth and impersonation grant, user shows back up on my web app with an authorization code
Take that authorization code and post it to oauth/token to get an access token for my target user
Take that access token and call oauth/userinfo to get the target user's IDs and URL
Create a JWT, sign using shared key pair between my web app and DS, and post it to oauth/token. Receive a 200 response with a seemingly-good-looking token.
This all seems to work correctly so far: all DS calls come back with 200s and data which is shaped as I expect.
Problems
The issue is that I can't actually successfully use that token from the final step to perform further action as the user who my app is impersonating. (I am being sure to use the base_url for the associated user.) When I request a GET from the suggested endpoint (brands), I receive this back:
{
"errorCode": "AUTHORIZATION_INVALID_TOKEN",
"message": "The access token provided is expired, revoked or malformed. Authentication for System Application failed."
}
The response which provided the authorization token includes an expires_in value in the thousands of seconds, and I'm performing all of these requests in serial in my web application. So, expiration or revocation should not be possible at this point. I also haven't touched the token at all, so I would expect it to be well formed.
Here's the code I'm using to post to that endpoint, if it's useful:
private async Task<IgnoreMe> GetBrands(UserInfoAccount account, AccessTokenResponse accessToken)
{
var client = _clientFactory.CreateClient("docusign");
var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri($"{account.BaseUri}/restapi/v2.1/accounts/{account.Id}/brands"),
};
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.AccessToken!);
var response = await client.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
return IgnoreMe.Fail;
}
return IgnoreMe.Succeed;
}
The args to this method are the values which came back from previous API calls: the AccessTokenResponse is from the impersonation call.
I've also tried sending similar requests to several other top-level user/account endpoints, and have received the same or similar errors for all of them.
What am I missing here?
Your flow is a mix if Auth Code Grant and JWT. You are using both.
The token from step 3 should work (But you can omit "impersonation" as it's not required for Auth Code Grant).
The token expires after 8 hours. That may be the reason for your error. You'll need to obtain a new one.
In this particular case, the problem was that I had used the wrong ID for the sub value when constructing the JWT.
Results from the oauth/userinfo endpoint I'm using come back structured as a top-level user ID which is associated with a bucket of accounts. I had used an account ID from one of those buckets rather than the top-level user ID.

Why would my REST service .NET clients send every request without authentication headers and then retry it with authentication header?

We happen to run a REST web service with API requiring that clients use Basic authentication. We crafted a set of neat samples in various languages showing how to interface with our service. Now I'm reviewing IIS logs of the service and see that the following pattern happens quite often:
a request comes, gets rejected with HTTP code 401
the same request is resent and succeeds
which looks like the first request is sent without Authorization headers and then the second one is sent with the right headers and succeeds. Most of the time the log record contains "user-agent" which is the same string we planted into our .NET sample.
So I assume the problem is with .NET programs only. The problem is not reproduced with our sample code so I assume the users somehow modified the code or wrote their own from scratch.
We tried contacting the users but apparently they don't want to invest time into research. So it'd be nice to find what the most likely scenario is which leads to this behavior of .NET programs.
Why would they do this? Why would they not attach the headers on the first attempt?
This is the default behavior of HttpClient and HttpWebRequest classes which is exposed the following way.
Note: Below text explains suboptimal behavior causing the problem described in the question. Most likely you should not write your code like this. Instead scroll below to the corrected code
In both cases, instantiate a NetworkCredenatial object and set the username and password in there
var credentials = new NetworkCredential( username, password );
If you use HttpWebRequest - set .Credentials property:
webRequest.Credentials = credentials;
If you use HttpClient - pass the credentials object into HttpClientHandler (altered code from here):
var client = new HttpClient(new HttpClientHandler() { Credentials = credentials })
Then run Fiddler and start the request. You will see the following:
the request is sent without Authorization header
the service replies with HTTP 401 and WWW-Authenticate: Basic realm="UrRealmHere"
the request is resent with proper Authorization header (and succeeds)
This behavior is explained here - the client doesn't know in advance that the service requires Basic and tries to negotiate the authentication protocol (and if the service requires Digest sending Basic headers in open is useless and can compromise the client).
Note: Here suboptimal behavior explanation ends and better approach is explained. Most likely you should use code from below instead of code from above.
For cases when it's known that the service requires Basic that extra request can be eliminated the following way:
Don't set .Credentials, instead add the headers manually using code from here. Encode the username and password:
var encoded = Convert.ToBase64String( Encoding.ASCII.GetBytes(
String.Format( "{0}:{1}", username, password ) ) );
When using HttpWebRequest add it to the headers:
request.Headers.Add( "Authorization", "Basic " + encoded );
and when using HttpClient add it to default headers:
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue( "Basic", encoded );
When you do that the request is sent with the right authorization headers every time. Note that you should not set .Credentials, otherwise if the username or password is wrong the same request will be sent twice both time with the wrong credentials and both times of course yielding HTTP 401.

WCF HTTP Headers Using HttpRequestMessageProperty and OperationContextScope

I realize this is a question that's been asked time and again, but I can't find a list of "gotchas" that I can take a look at.
I'm writing a WCF client that will consume an SAP web service, using a customBinding in my web.config with allowCookies set to false and support for reliable sessions enabled. I'm setting my HTTP headers as follows:
var authCookie = new System.Net.Cookie();
var wcfClient = new SomeWcfClient();
using (var context = new OperationContextScope(wcfClient.InnerChannel))
{
var cookies = new CookieContainer();
cookies.Add(authCookie);
var endPoint = new EndpointAddress("http://someDomain.test/");
var httpRequest = new System.ServiceModel.Channels.HttpRequestMessageProperty();
OperationContext.Current.OutgoingMessageProperties.Add(System.ServiceModel.Channels.HttpRequestMessageProperty.Name, httpRequest);
httpRequest.Headers.Add(HttpRequestHeader.Cookie, cookies.GetCookieHeader(endPoint.Uri));
wcfClient.PerformOperation();
}
When I use Fiddler, my HTTP header does not come across. I've tried creating dummy Referer and User-Agent headers, too, thinking that maybe something specific was happening with my cookie header, but even those other headers did not come across. Any thoughts? Where should I look next?
For this kind of stuff you should be implementing IClientMessageInspector - for some sample code see http://msmvps.com/blogs/paulomorgado/archive/2007/04/27/wcf-building-an-http-user-agent-message-inspector.aspx
See also (more current):
http://blog.khedan.com/2009/02/inspecting-messages-with.html
http://social.technet.microsoft.com/wiki/contents/articles/how-to-inspect-wcf-message-headers-using-iclientmessageinspector.aspx
http://yuzhangqi.itpub.net/post/37475/500654
http://wcfpro.wordpress.com/2011/03/29/iclientmessageinspector/
http://wcfpro.wordpress.com/2010/12/19/extended-wcf-preview/
http://wcfpro.wordpress.com/2011/01/31/realproxy/
http://wcfpro.wordpress.com/category/wcf-extensions/
http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/19500d14-78b7-4356-b817-fcc9abc2afcf/
http://msdn.microsoft.com/en-us/library/aa395196.aspx
WCF Content-Length HTTP header on outbound message
Adding Custom WCF header to Endpoint Programatically for Reliable Sessions
So, this issue was a lot different than we were expecting. I am still trying to find a fix, but at least I know root cause:
I am unable to send HTTP cookies to authenticate my requests; our SAP services use a MYSAPSSO2 token (HTTP cookie) for authentication. When trying to use WCF to connect to a Reliable Session-enabled SAP web service, our cookies don't get sent up front.
We are looking for a way to build a custom authentication provider that can use HTTP cookies.

Categories

Resources