OK, so I have been struggling with this for days. I have read every post I could find here, on GitHub, Microsoft, etc. None of the solutions I have tried, work.
System architecture:
- Blazor .NET WASM ASP.NET Core hosted application with Duende BFF Security Framework
- Duende Identity Server external instance
- Ocelot Api Gateway
- .NET6 remote Web Api
- All applications are deployed to an Interserver Shared IIS Server as individual domains
Problem: When calling GET methods everything works as expected. Response headers are returned as set in the solution. When calling any method in development, everything works as expected. When calling PUT or DELETE in Production, the call fails immediately with 405 Method Not Allowed.
Attempted solutions:
- Added CORS Policy to Api Gateway to allow any header / method. Checked logs. No call is reaching the gateway. That tells me it fails between client and server.
- Updated Server to add CORS Policy with SetIsOriginAllowed(origin => true).AllowAnyMethod().AllowAnyHeader().AllowCredentials()
- Updated web.config with the following sections:
<security>
<requestFiltering removeServerHeader="true">
<verbs allowUnlisted="true">
<add verb="POST" allowed="true"/>
<add verb="PUT" allowed="true"/>
<add verb="DELETE" allowed="true"/>
</verbs>
</requestFiltering>
</security>
- Updated custom security headers in Server using context.Response.Headers.Allow = "GET, POST, DELETE, PUT, OPTIONS";
I have attached two images showing the response headers for a GET and a PUT. The GET looks as expected. The PUT contains none of my set headers. Here is my server Program.cs:
using System.Net.Http.Headers;
using Azure.Identity;
using JMS.UI.Server.Extensions;
using JMS.UI.Server.Helpers;
using JMS.UI.Server.Settings;
using JMS.UI.Server.Static;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Extensions.Primitives;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
var builder = WebApplication.CreateBuilder(args);
// Add Azure Key Vault
var keyVaultEndpoint = new Uri(HIDDEN);
builder.Configuration.AddAzureKeyVault(HIDDEN);
// Add identity services
var idsSettings = new IdentityServerSettings { ClientPassword = builder.Configuration["HIDDEN"] };
#if DEBUG
idsSettings.DiscoveryUrl = "https://localhost:7102";
idsSettings.ClientName = "HIDDEN";
#else
idsSettings.DiscoveryUrl = "HIDDEN";
idsSettings.ClientName = "HIDDEN";
#endif
builder.Services.AddControllers();
builder.Services.AddRazorPages();
builder.Services.AddBff();
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
options.DefaultSignOutScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.Cookie.SameSite = SameSiteMode.Lax;
})
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.Authority = idsSettings.DiscoveryUrl;
options.ClientId = idsSettings.ClientName;
options.ClientSecret = idsSettings.ClientPassword;
options.ResponseType = OpenIdConnectResponseType.Code;
options.ResponseMode = OpenIdConnectResponseMode.Query;
options.MapInboundClaims = false;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.UsePkce = true;
options.Scope.Clear();
options.Scope.Add("JMS");
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add("offline_access");
});
// Add services to the container.
builder.Services.AddAutoMapper(typeof(AutomapperProfiles).Assembly);
builder.Services.AddSettingsServiceConfigurations(builder.Configuration);
builder.Services.AddServicesInjectors();
#if DEBUG
ApiEndpoints.ApiBaseUrl = new Uri("https://localhost:7200");
#else
ApiEndpoints.ApiBaseUrl = new Uri("HIDDEN");
#endif
builder.Services.AddHttpClient("JmsClient", options =>
{
options.BaseAddress = ApiEndpoints.ApiBaseUrl;
options.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue { MaxAge = TimeSpan.FromSeconds(2592000) };
}).AddUserAccessTokenHandler();
builder.Services.AddCors(options =>
{
options.AddPolicy("JmsPolicy", b => b.SetIsOriginAllowed(origin => true).AllowAnyMethod().AllowAnyHeader().AllowCredentials());
});
builder.Services.AddHttpContextAccessor();
builder.Logging.SetMinimumLevel(LogLevel.Error);
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error");
app.Use(async (context, next) =>
{
context.Response.Headers.XXSSProtection = "1; mode=block";
context.Response.Headers.XFrameOptions = "SAMEORIGIN";
context.Response.Headers.XContentTypeOptions = "nosniff";
context.Response.Headers.Add("Referrer-Policy", "strict-origin-when-cross-origin");
context.Response.Headers.Allow = "GET, POST, DELETE, PUT, OPTIONS";
context.Response.Headers.ContentSecurityPolicy =
"default-src 'self'; " +
"frame-ancestors 'none'; " +
"font-src 'self' https://fonts.googleapis.com https://fonts.gstatic.com https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; " +
"style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://fonts.googleapis.com https://fonts.gstatic.com; " +
"script-src 'self' 'unsafe-eval' 'unsafe-inline' https://cdn.jsdelivr.net https://use.fontawesome.com https://www.google.com https://maps.googleapis.com https://www.gstatic.com; " +
"img-src 'self' data: https://www.google.com https://maps.googleapis.com https://www.gstatic.com https://maps.gstatic.com; " +
"connect-src 'self' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://use.fontawesome.com https://maps.googleapis.com https://www.google.com https://fonts.googleapis.com https://fonts.gstatic.com https://www.gstatic.com; " +
"frame-src https://www.google.com https://maps.googleapis.com https://www.gstatic.com;";
await next();
});
}
app.UseCors("JmsPolicy");
app.UseHttpsRedirection();
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseBff();
app.UseAuthorization();
app.MapBffManagementEndpoints();
app.MapRazorPages();
app.MapControllers()
.RequireAuthorization()
.AsBffApiEndpoint();
app.MapFallbackToFile("index.html");
app.Run();