Best way to have one Web API forward request to other - asp.net

I have a few web services running on different servers, and I want to have one web service running "in front" of the rest to decide which web service (server) the request should be forwarded to based on header values.
The idea is that a client will send a request, say:
http://api.mysite.com/cars
The API at mysite.com will inspect the request, extract information from the API key (which is supplied in the headers) and redirect to the appropriate server, e.g.
http://server4.mysite.com/api/cars
Is this going to work? I'm concerned about how I will return the response (w/data) from "server4" to the client. Will the response only be returned back to the first server or will the client achieve that response?

Just run into the same task and have to add some more lines in addition to Yazan Ati answer.
[HttpPost]
[HttpGet]
[Route("api/TestBot/{*remaining}")]
public Task<HttpResponseMessage> SendMessage()
{
const string host = "facebook.botframework.com";
string forwardUri = $"https://{host}/api/v1/bots/billycom{Request.RequestUri.Query}";
Request.Headers.Remove("Host");
Request.RequestUri = new Uri(forwardUri);
if (Request.Method == HttpMethod.Get)
{
Request.Content = null;
}
var client = new HttpClient();
return client.SendAsync(Request, HttpCompletionOption.ResponseHeadersRead);
}

All you need to do is build a Web API DelegatingHandler like this:
public class ProxyHandler : DelegatingHandler
{
protected override async System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
UriBuilder forwardUri = new UriBuilder(request.RequestUri);
//strip off the proxy port and replace with an Http port
forwardUri.Port = 80;
//send it on to the requested URL
request.RequestUri = forwardUri.Uri;
HttpClient client = new HttpClient();
var response = await client.SendAsync(request,HttpCompletionOption.ResponseHeadersRead);
return response;
}
}

Related

How do you transform a cookie value into a header value in Ocelot

I am using a micro-services architecture in dotnet core.
I am putting Ocelot in front as an api-gateway (BFF).
My main web application uses cookie auth with the jwt token in the cookie.
This is for backwards compatibility.
All my new apis use bearer auth.
I would like to in Ocelot get the value out of the cookie and insert it into the header.
I have seen header values added in the configuration file.
This however will need a code implementation due to the dynamic nature.
What is the recommended approach for implementing this?
We had a requirement to change the header for our access token so in Ocelot we did this:
public class SecurityTokenHandler : DelegatingHandler
{
private const string Racoon = "Badger";
private readonly IHttpContextAccessor contextAccessor;
public SecurityTokenHandler(IHttpContextAccessor contextAccessor)
{
this.contextAccessor = contextAccessor;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var httpRequest = this.contextAccessor.HttpContext.Request;
var securityToken = httpRequest.GetSecurityTokenFromHeader();
if (!string.IsNullOrWhiteSpace(securityToken))
{
request.Headers.Authorization = new AuthenticationHeaderValue(Racoon , securityToken);
request.Headers.Remove(Constants.OurOldAccessToken);
}
return await base.SendAsync(request, cancellationToken);
}
}
Register like this:
services.AddDelegatingHandler<SecurityTokenHandler>(true);
Works great, single point to deal with, all our BFFs, MSs do not care!

POSTing data while redirecting to a third-party URL using Response.Redirect()

In ASP.Net Core 2.0, how can I POST data while redirecting to a third-party URL using Response.Redirect()?
Note: I have to POST this data without using the query-string.
Response.Redirect triggers a GET request which means that the only option is using a query string.
Can you trigger the redirection from the client (if any) in order to make a POST request?
You must use object if you want post data without using query string.
[HttpPost]
public IActionResult Search([FromBody] CustomerSearchRequestApiModel request)
{
if (request == null)
{
return BadRequest();
}
return Ok(request);
}
It is impossible to use Response.Redirect() to send Post request.
For a workaround, you could try HttpClient to send Post request and then return the reponse to the web browser with ContentResult as text/html.
Here is a demo code:
public async Task<ContentResult> HtmlView()
{
using (var formDataContent = new MultipartFormDataContent())
{
HttpClient client = new HttpClient();
Article article = new Article { ArticleName = "AN" };
formDataContent.Add(new StringContent("AN", Encoding.UTF8, "application/json"), "ArticleName");
using (HttpClient httpClient = new HttpClient())
{
HttpResponseMessage response = await httpClient.PostAsync(#"https://localhost:44393/Articles/Create", formDataContent);
return new ContentResult
{
ContentType = "text/html",
StatusCode = (int)response.StatusCode,
Content = await response.Content.ReadAsStringAsync()
};
}
}
}
Note
Change the HttpClient part to send the right request to your own third party url with validate parameters.

ADFS 4.0: Received invalid Client credentials

Any ideas why this can happen?
Our IT had ADFS updated from version 3 to version 4.
After the update our ASP.NET Core application gets following error:
Error Code:
"Unhandled remote failure. (OAuth token endpoint failure: Status: BadRequest;
Body: {\"error\":\"invalid_client\",\"error_description\":\"MSIS9623: Received invalid Client credentials. The OAuth client is not configured to authenticate using passed in client credentials.\"};)"
The request:
https://.../adfs/oauth2/authorize?client_id=d...4c&scope=&response_type=code&redirect_uri=https%3A%2F%2Flocalhost%3A44377%2F&state=CfDJ8...Og&resource=https%3A%2F%2Flocalhost%3A44377&redirect_url=https%3A%2F%2Flocalhost%3A44377
I tried also tried:
"grant_type"="authorization_code"
Someone an idea what the "client credentials" means in this context?
ADFS 4.0 throws an error if "client_secret" was sent. ADFS 3.0 has ignored that value.
The UseOAuthAuthentication sends always an "client_secret". My dirty solution is to intercept the http request and remove the "client_secret". If someone has a better solution...
if (securityService.IsOAuthEnabled)
{
HttpClientHandler clientHandler = new HttpClientHandlerInterceptor(){};
var options = securityService.GetOAuthOptions();
options.BackchannelHttpHandler = clientHandler;
app.UseOAuthAuthentication(options);
}
HttpClientHandlerInterceptor:
public class HttpClientHandlerInterceptor : HttpClientHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.Content is FormUrlEncodedContent)
{
var x = ((FormUrlEncodedContent) request.Content).ReadAsStringAsync().Result;
var contenttype = request.Content.Headers.ContentType.MediaType;
x = x.Replace("client_secret=will+be+ignored&", "");
request.Content = new StringContent(x, Encoding.UTF8, contenttype);
}
return base.SendAsync(request, cancellationToken);
}
}
When you configure the application on the ADFS side via the wizard, you get a clientId.
This is the clientId that you pass in the request.
Check that you are passing the correct clientId.
Also look in the ADFS error log.
Notice in your request string this: response_type=code
When I commented out the UseOAuthe2CodeRedeemer from the ConfigureAuth function that was in Startup.Auth.cs, it alleviated the problem at hand.
See below:
// code_grant is present in the querystring (&code=<code>).
//app.UseOAuth2CodeRedeemer(
// new OAuth2CodeRedeemerOptions
// {
// ClientId = AuthenticationConfig.ClientId,
// ClientSecret = AuthenticationConfig.ClientSecret,
// RedirectUri = AuthenticationConfig.RedirectUri
// }
//);

HttpClient client-side message handlers

The HttpClient custom Client Message Handlers insert themselves in the client-side pipeline when making web api calls. This info is from this article
The author says that these message handlers go into action as the request is being sent out and also when the response is received back from the server.
I understand these client message handler's role as request is being sent out from client to the Server because the sendAsync method provides the request object. One can add custom request headers, etc.
I do not understand the usefulness of these message handlers AFTER the response is received from the server because the sendAsync method does not provide access to the response object. So, I am not sure how these client side message handlers are useful on the return journey of the request.
I am obviously missing something here.
Actually you do get access to the response message inside the message handler. For example, in the following handler, I am logging outgoing request and incoming response. Now as long as I use this instance of HttpClient, all calls made through it write traces of requests and response.
HttpClient client = new HttpClient(new LoggingHandler(new HttpClientHandler()));
public class LoggingHandler : DelegatingHandler
{
public LoggingHandler(HttpMessageHandler innerHandler)
: base(innerHandler)
{
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Console.WriteLine("Request:");
Console.WriteLine(request.ToString());
if (request.Content != null)
{
Console.WriteLine(await request.Content.ReadAsStringAsync());
}
Console.WriteLine();
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
Console.WriteLine("Response:");
Console.WriteLine(response.ToString());
if (response.Content != null)
{
Console.WriteLine(await response.Content.ReadAsStringAsync());
}
Console.WriteLine();
return response;
}
}

Securing ASP.NET MVC controller action which returns JSON

I have an MVC3 application, and my controller actions are secured using the [Authorize] attribute. So far, so good, forms auth works great. Now I want to add a JSON API to my application so some actions are accessible to non-browser clients.
I'm having trouble figuring out the 'right' design.
1) Each user has secret API key.
2) User ID 5 calls http://myapp.com/foocontroller/baraction/5?param1=value1&param2=value2&secure_hash=someValue. Here, secure_hash is simply the SHA1 hash of param1 and param2's values appended with the secret API key for the user
2) /foocontroller/baraction will be decorated with [CustomAuthorize]. This will be an implementation of AuthorizeAttribute which will check if the request is coming in as JSON. If it is, it will check the hash and see if it matches. Otherwise, if the request is HTML, then I call into existing authorization.
I am not at all sure if this will work. Is it normal to pass a secure hash in the query string or should I be passing it in as an HTTP header? Is it better to use HTTP basic auth instead of a hash made using the secret API key?
Tips from anyone who has made a web API using ASP.NET MVC would be welcome!
I pass the secret API key along with username and password in the request body. Once authorized, a token is generated and the client has to pass that in the Authorization header. This gets checked in the base controller on each request.
Client calls myapp.com/authorize which return auth token.
Client stores auth token locally.
Client calls myapp.com/anycontroller, with authtoken in Authorization header.
AuthorizeController inherits from controller.
Anycontroller inherits from a custom base controller which performs the authorization code.
My example requires the following route which directs POST requests to an ActionResult named post in any controller. I am typing this in by hand to simplify it as much as possible to give you the general idea. Don't expect to cut and paste and have it work :)
routes.MapRoute(
"post-object",
"{controller}",
new { controller = "Home", action = "post" {,
new { httpMethod = new HttpMethodConstraint("POST")}
);
Your auth controller can use this
public class AuthorizationController : Controller
{
public ActionResult Post()
{
string authBody;
var request = ControllerContext.HttpContext.Request;
var response = ControllerContext.HttpContext.Response;
using(var reader = new StreamReader(request.InputStream))
authBody = reader.ReadToEnd();
// authorize based on credentials passed in request body
var authToken = {result of your auth method}
response.Write(authToken);
}
}
Your other controllers inherit from a base controller
public class BaseController : Controller
{
protected override void Execute(RequestContext requestContext)
{
var request = requestContext.HttpContext.Request;
var response = requestContext.HttpContext.Response;
var authToken = Request.Headers["Authorization"];
// use token to authorize in your own method
var authorized = AmIAuthorized();
if(authorized = false) {
response.StatusCode = 401;
response.Write("Invalid token");
return;
}
response.StatusCode = 200; // OK
base.Execute(requestContext); // allow inheriting controller to continue
}
}
Sample code to call the api
public static void ExecutePostRequest(string contentType)
{
request = (HttpWebRequest)WebRequest.Create(Uri + Querystring);
request.Method = "POST";
request.ContentType = contentType; // application/json usually
request.Headers["Authorization"] = token;
using (StreamWriter writer = new StreamWriter(request.GetRequestStream()))
writer.Write(postRequestData);
// GetResponse reaises an exception on http status code 400
// We can pull response out of the exception and continue on our way
try
{
response = (HttpWebResponse)request.GetResponse();
}
catch (WebException ex)
{
response = (HttpWebResponse)ex.Response;
}
finally
{
using (StreamReader reader =
new StreamReader(response.GetResponseStream()))
responseText = reader.ReadToEnd();
httpcontext = HttpContext.Current;
}
}

Resources