C# WebAPI Top Message Handling - c#

I have a WebAPI service that uses ADFS (not important to the question but it's microsoft's active directory authentication service).
A user sends a request to the server and since he isn't authenticated he is redirected to the ADFS's login page. Next time he sends a request to the server he will send an authentication cookie which will allow him to skip the ADFS login page.
The service is being accessed by a different domain (CORS) but I already fixed that issue. GET request are processed easily.
My problem is with OPTIONS requests, since my service is a on a different domain then the website's domain. An OPTIONS request is sent before each POST request. All OPTIONS requests do not include cookies so the request is being redirected to the ADFS login page.
I wanted to create a message handler which will execute even BEFORE the ADFS, Like a message handler that will be the very first code which will run of the message so I could check if the method of the request is OPTIONS (in which case I will simply return a response)
I tried using an example I found on message handling but it's being executed too late, the ADFS' message handler is way above it
public class MessageHandler2 : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
// Create the response.
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent("Hello!")
};
// Note: TaskCompletionSource creates a task that does not contain a delegate.
var tsc = new TaskCompletionSource<HttpResponseMessage>();
tsc.SetResult(response); // Also sets the task state to "RanToCompletion"
return tsc.Task;
}
}
Does anyone know how can I handle a message as high up the pipe as possible? Maybe even without message handlers

If you take a look at the following tutorial...
HTTP Message Handlers in ASP.NET Web API
You will see...
Message handlers are called in the same order that they appear in
MessageHandlers collection. Because they are nested, the response
message travels in the other direction. That is, the last handler is
the first to get the response message.
You need to make sure you register your handler before ADFS. Take a look at your setup for your web api

Related

IdentityServer4 custom token validation called only once

I am currently working on the logic of custom token validation. I need to deactivate the token when the user's password is changed (change-password endpoint is public).
I have implemented the ICustomTokenRequestValidator interface and resolved my class via DI
.AddCustomTokenRequestValidator<TokenHashValidatorService>();
However, I can see the following problem, my implementation of ICustomTokenRequestValidator only works when I generate a token and during only the first request to my API.
In logs I see the following information:
JWKS request from log
During first request to API request to /.well-known/openid-configuration and /.well-known/openid-configuration/jwks is sent. But when I send a second, third, etc. requests my breakpoint in TokenHashValidatorService is skipped.
Is there any way I can forcefully initiate second /.well-known/openid-configuration and /.well-known/openid-configuration/jwks requests?
Or maybe I can somehow mark that "token validation needed" during the change-password flow?
I'm really stuck and out of options, I've read all the articles out there, any ideas?

Redirect to a page with api response in ASP.NET Core MVC

I have an api controller with a http GET method. This method is invoked from a frontend. In response to this, I want to redirect user to dashboard page and also provide some login response object. When a request comes into this method, it comes from a different page.
The request comes to this method with a path api/accounts/loginobject when a third party identity servers sends oauth2 authentication. This is a redirect url method.
[HttpGet]
[Route("LoginObject")]
public async Task<IActionResult> getLoginObject()
{
var externalLoginInfo = await this.signInManager.GetExternalLoginInfoAsync().ConfigureAwait(false);
// parse token here
}
I tried below code and it works. But I don't know if we can send response object in the below response. I want to attach token in the headers and some info in the response body.
// dashboard is a ui page not another controller action
return Redirect("/dashboard");
The front end app is an angular app part of ASP.NET Core MVC project. I have a third party identity server which sends oauth2 result back to this controller.
The following will set header but it will be lost when the redirection to html page /dashboard happens. Still I am not able to respond to ui with access token
this.Response.Headers.Add("Authorization", $"Bearer {accessToken}");
Update:
I solved this temporarily by passing token in query string. It looks more like a hack for now. Once received it in ui, i will set it in web app local storage for subsequent api calls.
https://mywebsite/accesstokencallback?access_token=eybcsdfk23klsd-3dsfjs

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.

How to Consume WebApi WebService that Requires Redirect for Login

We have a desktop application that requires authentication with a server in order to operate. This application prepares and sends a query to a webservice, the user is prompted from this webservice to log in and the webservice returns an XML document with application subscription information (Software-as-a-Service Subscription License).
I've created a WebApi webservice that does the following:
Accept incoming request at /api/client?[MACHINEINFOINQUERYSTRING]
Redirect to external authentication provider (think GoogleId or
similar)
Authentication provider sends information back to
/api/subscription/[AUTHENTICATIONID]
The /api/subscription endpoint returns an XML document after pulling info from the servers (or including appropriate error message).
This webservice works and the XML document can be viewed in the browser. I've created a website with a default.aspx to test this, automatically redirecting to the /api/client and it does display this XML document in the browser.
The desktop application properly makes the initial call, redirects through an embedded browser to the login page, and receives an XML, but this XML cannot be parsed. The application team simply gets a "download" option for the XML but cannot capture the response for stuffing into an XmlDocument object. I've attempted to create an example application to instruct the app team, but have had no success.
Questions:
Do I have this architecture fundamentally wrong or do we simply not know how to consume the response properly?
How do I capture and consume the XML that is successfully returned?
As an example of what I've tried:
string requestString = string.Format("http://[server]/api/client?{0}", HttpUtility.HtmlEncode(queryString));
Response.Redirect(requestString);
This works in the browser, displays the login page, allows for input, redirects to the subscription endpoint which then prepares and delivers the XML to the browser. Unfortunately, this is unusable by a consumer.
HttpWebRequest request = WebRequest.Create(requestString) as HttpWebRequest;
request.AllowAutoRedirect = true;
request.MaximumAutomaticRedirections = 20;
request.AuthenticationLevel = System.Net.Security.AuthenticationLevel.None;
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
This doesn't work. The response.ResponseUri has the properly formatted address of the OAuth service (step 2 above). It does not display the login page to the user even though this is all initiated through a browser.
I've also tried using a WebRequest POST, HttpClient PostAsync and several other methods, but:
The response URL is simply the location of the login page. If I string together 3 WebRequest/WebResponse pairs, it fails because the user isn't being properly authenticated at the first request / initial redirect.
What does work in my default.aspx:
I haven't found an example online for my specific needs, but the pattern must exist in practice as plenty of websites utilize OAuth style logins. I've utilized webservices (like OData endpoints) that also require logins, so this pattern must exist for webservices too. I do send back a properly formatted XML document. We just don't know how to capture and consume that document.
Any examples of a similar architecture would be highly appreciated! Or pointing me in the right direction.
Edit ---
I'm thinking that somehow the request.GetResponse() isn't really allowing for redirects and/or since it's an HttpWebRequest, there's no way it will allow for user input.
What's the proper way to make this call and consume the XML from another application? the XML is delivered properly in a browser window (with Response.Redirect) but no login window opens using an HttpWebRequest.
The answer is: This architecture is fundamentally wrong.
OAuth architecture leaves the authorization to the client, which then sends an authorization token to all subsequent services that it requires. The services are simple endpoints and do not contain any authentication logic, (although the service itself is allowed to validate the authentication token with the OAuth server).
The proper sequence of events for this answer should be:
1) Desktop Application makes OAuth authentication request to authentication server.
2) A successful response includes an authorization token which contains identity information, permissions, validity period, etc.
3) The desktop app then requests information from the WebApi service, sending in the request that token.
4) WebApi takes this authentication token, validates it (in my case against a certificate) and may even query the OAuth server again to ensure that the token is still valid.
5) If valid, the web service gathers the data and sends it back to the server.
My problem was I was expecting the subscription service itself to be able to open a web browser, prompt for a login, and then continue the request to another endpoint (2 redirects after initial request). I was in effect, breaking both the WebApi and OAuth 2 designs. Although it worked from the browser, it was not consumable from an application.
After redesigning to this simpler pattern, my web service is now consumable.

Credentials on HttpClient is not validated after first successful REST call

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

Categories

Resources