Enable preflight CORS between C# and Angular - c#

I am trying to make CORS work for request that require a preflight check. In this case I am making a POST-request to the back-end with an extra header.
Angular:
let myHeaders = new HttpHeaders();
myHeaders = myHeaders.append('Content-Type', 'application/json');
return this.http.post<UserOrder>(`${this.apiURL}/Order/PlaceOrder`, JSON.stringify(payload), {headers : myHeaders}); //email);
C# API:
[HttpPost("PlaceOrder")]
public GenericResponse PlaceOrder(UserOrderInsertModel userOrder)
{
return _orderProvider.PlaceOrder(new UserOrder());
}
Because of the preflight check it first makes an OPTIONS-request. When I do not define a separate options-endpoint in the backend I get a 405 Method Not Allowed. This made me think I needed a separate options-endpoint in my back-end on top of the post-endpoint.
[HttpOptions("PlaceOrder")]
public ActionResult PlaceOrderOptions(UserOrderInsertModel userOrder)
{
return Ok();
}
After adding this I run into a 415 Unsupported Media Type (on the options call). This is probably because the Content-Type header is not supported for a HttpOptions request.
I feel like the extra options endpoint shouldt be neccessary at all. The CORS-middleware I currently use is as follows:
httpContext.Response.Headers.Add("Access-Control-Allow-Origin", "http://localhost:4200");
httpContext.Response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
httpContext.Response.Headers.Add("Access-Control-Allow-Headers", "*");
httpContext.Response.Headers.Add("Access-Control-Allow-Credentials", "true");
Extra info: the CORS in general did already work. Cross-site scripting with a GET request and the Access-Control-Allow-Origin header went well. It is just that I cannot get the POST/OPTIONS combo to work.
Edit: in the Startup.cs I first tried to use app.UseCors() as follows:
app.UseCors();
options => options.WithOrigins("http://localhost").AllowAnyMethod()
);
This unfortuantely didnt work so then I resorted to inserting the middleware as described above.
Any help would be highly appreciated!

Ok, thanks a lot everybody. The problem was most likely in the middleware I was using. This was a workaround I added because the UseCors() initially didnt work. This was probably because I didnt use app.AddCors() initially. Without the custom middleware it makes everything a lot easier!

A quick look at the documentation will clarify few things. I share few extracts below
Because of the preflight check it first makes an OPTIONS-request. When I do not define a separate options-endpoint in the backend I get a 405 Method Not Allowed. This made me think I needed a separate options-endpoint in my back-end on top of the post-endpoint.
Preflight Request
For some CORS requests, the browser sends an additional OPTIONS request before making the actual request. This request is called a preflight request.
The browser can skip the preflight request if all the following conditions are true
The request method is GET, HEAD, or POST.
The app doesn't set request headers other than Accept,
Accept-Language, Content-Language, Content-Type, or Last-Event-ID.
The Content-Type header, if set, has one of the following values:
application/x-www-form-urlencoded multipart/form-data text/plain
I feel like the extra options endpoint shouldt be neccessary at all.
Except you are using CORS with endpoint routing, ASPNET Core should respond to appropriate preflight request when core is enabled on startup.
Condition for Automatic preflight
When the CORS policy is applied either:
Globally by calling app.UseCors in Startup.Configure.
Using the [EnableCors] attribute.
ASP.NET Core responds to the preflight
OPTIONS request.
Enabling CORS on a per-endpoint basis using RequireCors currently does not support automatic preflight requests.
Enable Cors on Starup
In configure service
public void ConfigureServices(IServiceCollection services)
{
.
//other codes
.
services.AddCors(options =>
{
options.AddDefaultPolicy(
builder => //check corsbuilder for additonal config
{
builder.WithOrigins("http://example.com",
"http://www.contoso.com;
});
});
.
//other codes
.
}
and in Configure method
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
.
//Other codes
.
app.UseCors();
.
//Other codes
.
}
full documentation here for 3.0 https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-3.0#ecors

Enables Cors and make sure to AllowAnyHeader and AllowAnyMethod
Using .Net Core
services.AddCors(options =>
{
options.AddDefaultPolicy(builder =>
{
builder.WithOrigins(Configuration.GetValue<string>("JwtConfig:corsWhiteListUrl"))
.AllowAnyHeader()
.AllowAnyMethod();
});
});

Make sure to call UseCors() before UseEndpoints().
Another hint: If you have credentials, the wildcard do not work as expected.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods

Related

Securing Blazor WASM and API with Auth0. CORS Policy issue

I've been following the tutorial at Auth0 for securing a Blazor WASM and API with Aut0, which is found here --> https://auth0.com/blog/securing-blazor-webassembly-apps/
Securing the app works fine, but adding the API gives me issues. As soon as I add the authenticate attribute to the API Controller it results in this:
fetchdata:1 Access to fetch at
'https://localhost:7226/weatherforecast' from origin
'https://localhost:7298' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested
resource. If an opaque response serves your needs, set the request's
mode to 'no-cors' to fetch the resource with CORS disabled.
I've added a policy to allow the app in the program.cs
builder.Services.AddCors(options =>
{
options.AddPolicy("Open", builder => builder.WithOrigins("https://localhost:7298").AllowAnyMethod().AllowAnyHeader());
});
I've played around with the program.cs and also added app.UseCors before authentication/authorization (as a provided solution I found online), which then results in another issue.
Failed to load resource: the server responded with a status of 401 ()
blazor.webassembly.js:1 crit:
Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: The input does not contain any JSON tokens. Expected the input to start with a valid JSON
token, when isFinalBlock is true. Path: $ | LineNumber: 0 |
BytePositionInLine: 0. System.Text.Json.JsonException: The input does
not contain any JSON tokens. Expected the input to start with a valid
JSON token, when isFinalBlock is true. Path: $ | LineNumber: 0 |
BytePositionInLine: 0.
That error seems to indicate an issue with the bearertoken not being set, but it is setup using BaseAddressAuthorizationMessageHandler.
builder.Services.AddHttpClient("APIClient", client =>
{
client.BaseAddress = new Uri("https://localhost:7226");
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json");
}).AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
I've added the project to GitHub, if more details on the code is of interest
https://github.com/obrungot/BlazorAuthenticationPlayGround.git
AddCors doesn't actually do anything except for adding a policy. It's not using that policy until you specify it somewhere. You can specify a global policy by using app.UseCors("Open"), add the policy to the endpoint routing e.g. app.MapGet("/test", () => Results.Ok("test")).RequireCors("Open") or for controllers by using an attribute like [EnableCors("Open")].
That you received a 401 suggested that Cors in general seems to work, however Cors also needs explicit permission to keep the Authorization header. This is done by adding "AllowCredentials()" to the policy like this:
builder.Services.AddCors(options =>
{
options.AddPolicy("Open", builder => builder
.WithOrigins("https://localhost:7298")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
);
});
I hope this helps!
Edit:
Sorry. I somehow missed the github repository link. The reason why it doesn't work is that BaseAddressAuthorizationMessageHandler is meant for hosting the API within the BaseAddress of the app. So in your case only calls to https://localhost:7298/ will include the token. You might either choose to host the API together with the app in the same process (this can be set up by choosing "ASP.NET Core Hosted" in the template) or use a custom AuthorizationMessageHandler which you can learn about here https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/additional-scenarios?view=aspnetcore-7.0#custom-authorizationmessagehandler-class
In your case this would look like this:
public class ApiAuthorizationMessageHandler : AuthorizationMessageHandler
{
public ApiAuthorizationMessageHandler(IAccessTokenProvider provider, NavigationManager navigationManager) : base(provider, navigationManager)
{
ConfigureHandler(new[] { "https://localhost:7226" });
}
}
When it comes to the cors the order is important
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
If set up like that "[EnableCors("Open")]" on the controller should work.

All PUT Requests are blocked by CORS policy

I got a .NET 5 API with some cors-policies.
This are my CORS-settings:
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.AddMemoryCache();
services.AddCors(options =>
{
options.AddPolicy("cors",
builder => builder
.WithOrigins(
"http://localhost:4200",
"http://127.0.0.1:4200",
"http://rev-staging.myhost.ch")
.WithMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.AllowAnyHeader()
.AllowCredentials()
);
});
...
}
and of course app.UseCors("cors"); in Startup.Configure(IApplicationBuilder, WebHostEnvironment )
When I execute my methods on localhost, everything works.
As soon as I publish to api-rev-staging.myhost.ch and execute them on live-server, for all PUT-requests I get
Access to XMLHttpRequest at 'http://api-rev-staging.myhost.ch/api/v1/Vacancies/7' from origin 'http://rev-staging.myhost.ch' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
(I changed the URL because it's company URL)
All GET and POST requests do work - just the PUT-requests don't work. They neither work when executing from angular front-end on http://rev-staging.myhost.ch nor when executing from postman on my local machine - but the GET and POST still work.
Any idea what is wrong here?
Edit: maybe it's important. Backend runs on IIS, same server like frontend, just another hostname.
Try to check if your server accept PUT and DELETE for Web API.
Out of the box these verbs are disabled.
Some documentation here:
https://inthetechpit.com/2019/02/24/enable-put-and-delete-http-verbs-for-webapi-with-cors-on-iis-8-5/

Getting CORS errors on HTTP Post call on frontend

I wrote an Asp.Net Core api and so far it has been working great, however when I try to send a post request it gives me Access to XMLHttpRequest at 'https://localhost:44339/api/drawing/checkout' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
I have CORS enabled on my backend(startup.cs) like so :
services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://localhost:4200")
.AllowCredentials()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
and app.UseCors(MyAllowSpecificOrigins);
I've already wasted a couple of days on this, my GET requests work, as well as my POST requests using Insomnia. Could someone please help me with the Angular part? here's the code I have
On drawing.service.ts:
test(param) {
return this.http.post(this.url + '/checkout', param, {
headers: { 'Content-Type': 'application/json' },withCredentials:true
});
}
And on my component ts:
test() {
let stringfiedArray = JSON.stringify(this.viewerPerm);
console.log(stringfiedArray);
this.drawingSearchService.test(stringfiedArray).subscribe();
}
You need to create a proxy.conf.json file on your Angular project including the following content
{
"/api": {
"target": "http://localhost:4200",
"secure": false,
"changeOrigin": true,
"pathRewrite": {
"^/api": ""
}
}
}
Where you call the service you will reference the proxied api as follow:
this.http.get(`api/checkout`);
remember to use ` character and not single quotes.
When you run your application be sure to add the proxy file
ng serve --proxy-config proxy.conf.json
GUIDE
On the backend, for internal use only, try setting it as follow
services.AddCors(options =>
{
options.AddPolicy("AllowAnyOrigin",
builder =>
{
builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
});
});
First, I am going to assume that your current code is working fine with another API service, and does POST requests just fine. I hope you have tested your front end code for sure, and have isolated the problem is only with your API server.
Second, instead of a named policy, try using default policy, just to see if that makes a difference. it would look like this.
//lets add some CORS stuff
services.AddCors(options =>
{
options.AddDefaultPolicy(builder => {
builder.WithOrigins("http://localhost:4200");
builder.AllowAnyMethod();
builder.AllowAnyHeader();
builder.AllowCredentials();
});
});
and later, in Configure,
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors(); //it may not matter, but try to put cors between routing and auth.
app.UseAuthorization();
Access to XMLHttpRequest at 'https://localhost:44339/api/drawing/checkout' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
A CORS preflight request using the HTTP OPTIONS method is used to check whether the CORS protocol is understood and a server is aware using specific methods and headers.
And the OPTIONS requests are always anonymous, you mentioned that you enabled NTML authentication, which would cause server not correctly respond to the preflight request.
For more information about "CORS preflight request", please check: https://learn.microsoft.com/en-us/iis/extensions/cors-module/cors-module-configuration-reference#cors-preflight-request
If you'd like to run your app(s) on local for testing purpose with CORS, to fix this issue, you can try to enable anonymous authentification to allow anonymous access.
Besides, if you host your app(s) on IIS server, to fix this issue, you can install IIS CORS module and configure CORS for the app.

Asp.net mvc core 2.1 response caching not working [duplicate]

I want to use server-side response caching (output cache) with asp.net core 2.0 and found out about Response Caching Middleware and wanted to give it a try with a brand new asp.core mvc project.
Here is the description from the link above which makes me think this could be used like output cache.
The middleware determines when responses are cacheable, stores responses, and serves responses from cache.
Here is how my startup.cs looks like.
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddResponseCaching();
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseResponseCaching();
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
and here is the HomeController.cs
[ResponseCache(Duration = 60)]
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
public IActionResult About()
{
ViewData["Message"] = "Your application description page.";
return View();
}
public IActionResult Contact()
{
ViewData["Message"] = "Your contact page.";
return View();
}
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
there is also a timestamp at the bottom of _Layout.cshtml file so i can tell when the page is rendered, like below.
<p>© 2018 - ResponseCachingMiddleware - #DateTime.UtcNow</p>
Cache-Control headers seem to be fine, this is what I get in headers when I load the page but time stamp keeps getting updated on every refresh every second.
Cache-Control:public,max-age=60
What I'm understanding from MS documentations is Response Caching Middleware is the server-side caching mechanism that takes care of caching the response while Response Caching seems to be just a filter to manipulate response headers for caching.
Can't tell if there is something wrong with my understanding or code and I wanna complain that I'm feeling this way too often since I started prototyping with ASP.Net Core. Maybe you could also suggest better resources as a side topic.
I've checked out this post before
ASP.NET Core 2.0 - Http Response Caching Middleware - Nothing cached
Also checked this out but it seems like the only difference is I'm using mvc.
https://github.com/aspnet/ResponseCaching/blob/dev/samples/ResponseCachingSample/Startup.cs
Thanks
Edit: I'm seeing the message below in the output window, cannot find anything about it on google except the few places I already checked for response caching middleware.
Microsoft.AspNetCore.ResponseCaching.ResponseCachingMiddleware:Information:
The response could not be cached for this request.
Note: I wish I could create #response-caching-middleware tag. Not sure #responsecache is relevant.
I had the same issue, I was about to pull my hairs over it, I'd set app.UseResponseCaching(); as well as services.AddResponseCaching(); and add ResponseCache on top of my action exactly like what was told in Microsoft official Docs, despite the the cache-controll header was set correctly on response returning from server but still nothing cached at server-side.
After couple of hours of sweating on this issue I figured out where the problem arises and why nothing cached at server.
Browsers by default set cache-controll value to max-age=0 for the request (if the request is not caused by back or forward) even though you set cache-controller correctly in your response by adding ResponseCache attribute on top of you action (or controller) since the cache-controller sent by request is set to max-age=0, the server is unable to cache response, I think this must be added to list of Response Caching limitation as well
Anyway you can override browser default behavior by adding few line of code right before calling app.UseResponseCaching(); on the other hand you need to add a custom middle-ware to modify request cache-control header value before calling app.UseResponseCaching();.
See code below, worked for me hope work for you too
app.Use(async (ctx, next) =>
{
ctx.Request.GetTypedHeaders().CacheControl = new Microsoft.Net.Http.Headers.CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(60)
};
await next();
}
);
app.UseResponseCaching();
for ensuring that ResponseCaching works as expected you can also use postman but you must set 'Send no-cache Header' to off in the setting, see image below
I had this same confusion recently.
ASP.Net Core's ResponseCaching does provide both client-side caching (through HTTP response headers) & server-side (through a memory cache'd middleware that short-circuits other middlewares if the response is in the cache). The server-side portion reads the HTTP response cache headers to determine if it should do server-side caching (similar to what an ISP or CDN might do).
Unfortunately, debugging the server-side ResponseCaching is tricky because it has weird rules & there's not adequate logging. In my case I pulled down Microsoft's source code to step through it & find the issue with my code.
The note you found in the output window "The response could not be cached for this request" is a clue.
There's 2 parts to the server-side caching of a request. The server has to prime the cache the first time the url is requested. It will serve the cached version the 2nd time it's requested. Pay attention to when the error message shows up, if it's on the 1st or 2nd request. That'll tell you if it couldn't be stored in the cache or if it couldn't be retrieved from the cache.
The rules for both storage & retrieval are in this source code file:
https://github.com/aspnet/ResponseCaching/blob/3bf5f6a1ce69b65c998d6f5c739822a9bed4a67e/src/Microsoft.AspNetCore.ResponseCaching/Internal/ResponseCachingPolicyProvider.cs
Your "Cache-Control:public,max-age=60" header should match these rules just fine.
My guess is you actually had it working, but didn't know how to test it correctly.
There is a counter-intuitive portion of ResponseCaching noted in this issue: https://github.com/aspnet/Home/issues/2607
Essentially, if the browser sends a no-cache or no-store header (when you hit CTRL+F5 or have your debugger tools open), ASP.Net Core's ResponseCaching will honor the browser's request & re-generate the response.
So, to test if your code was working you probably loaded the page, which primed the cache, then you hit CTRL+F5 to force-refresh your browser & you expected the server-side to respond with a cached entry rather than running your WebAPI code. However, it honored the no-cache request header & bypassed the cache (& wrote that message in your output log).
The way to test this would be to clear your browser cache in-between requests (or switch to incognito), rather than using CTRL+F5.
On a side note, honoring the no-cache/no-store request headers was probably a poor design choice since ASP.Net Core's ResponseCache will most likely be used by a server who owns the response, rather than an intermediary cache like a CDN/ISP. I've extended the base ResponseCache with an option to disable honoring these headers (as well as serialize the cache to disk, rather than in-memory only). It's an easy drop-in replacement for the default cache.
You can find my extension here:
https://github.com/speige/AspNetCore.ResponseCaching.Extensions
https://www.nuget.org/packages/AspNetCore.ResponseCaching.Extensions
There are also a few other other gotchas with ResponseCaching to watch out for which you may have already read about in the blog urls you posted. Authenticated requests & responses with set-cookie won't be cached. Only requests using GET or HEAD method will be cached. If the QueryString is different, it'll make a new cache entry. Also, usually you'll want a "Vary" header to prevent caching if certain conditions of a request differ from the previously-cached request (example: user-agent, accept-encoding, etc). Finally, if a Middleware handles a request it'll short-circuit later Middlewares. Make sure your app.UseResponseCaching() is registered before app.UseMVC()
If the Cache-Control header is coming through, then it's working. That's all the server can do from that perspective. The client ultimately makes the decision whether or not to actually cache the resource. Sending the header doesn't force the client to do anything; in fact, the server, in general, cannot force the client to do anything.

.net core 2.0 cookie authentication getting stuck in infinite redirect loop when trying to access over https

I have just moved my code to our QA environment which uses https and what was working in Dev is not working in QA because the browser gets stuck in an infinite redirect loop. Our load balancer forces https so when the login redirect happens from code, which for some reason it's trying to redirect to http instead of https, the load balancer is stopping it and adding https again which causes the infinite loop. The question I have is why is this code not just redirecting to https, the path is relative in the ConfigureServices() method. I've looked at it in fiddler, and it is indeed adding the FQDN for the redirect with http instead of https.
Is there some property I need to add to options here to allow https redirects?
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/Account/LogIn";
options.LogoutPath = "/Account/LogOff";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
}
thanks.
We just use:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
... //settings and logging initialization
app.Use((context, next) =>
{
context.Request.Scheme = "https";
return next();
});
... //all the rest middleware calls
}
and it helps in most situations under OWIN and .Net Core up to 2.0
Based on #Programmer's suggestion in the comments to the OP, I took a look at this: https://codeopinion.com/configuring-asp-net-core-behind-a-load-balancer/ It describes my situation exactly (ssl termination at the load balancer and the .net core 2.0 app redirecting to http for login). I then tried making the request through the LB with the header the article suggests and adding in the Configure() method of the Startup class this piece of code:
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedProto
});
what was interesting is that when I made a request including the proto header:
X-Forwarded-Proto:https
from outside the LB, it passed that header through to the app and it worked great, no more infinite redirect loop. However when our infrastructure guys added that header to the request that the LB makes to the internal nodes behind the LB, I was getting a redirect to https, yay, but it was also prepending the ip address to the redirect URL (we have a netscaler LB). Apparently by default when you add a custom header, there's a checkbox to include the IP to the internal node and that had to be unchecked. After that was done, we're in business.
thanks again #Programmer for your help. You definitely pointed me in the right direction.
For .net core 2.1 and up with azure authentication try this code.
services.Configure(AzureADDefaults.CookieScheme, options =>
{
options.Cookie.SameSite = SameSiteMode.None;
});
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));

Categories

Resources