Problem in enabling CORS in asp net core web api v3.0 - asp.net-core

I am using asp net core 3.0 in my web API project. I have created various API's and all are accessible via Swagger or Postman. But when trying to access the same via any other client like React, Method not allowed (405 error code) is received. On investing further, I find out that at first, OPTION request is received from the React application and the net core web API application is giving the 405 status code. Further, I find out that I need to enable all the methods as well as origins from the net core application to accept all types of requests otherwise it will not accept OPTION request. To achieve this, I enabled CORS policy in startup.cs file but still had no luck. Following is my startup.cs file:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
var elasticUri = Configuration["ElasticConfiguration:Uri"];
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.Enrich.WithExceptionDetails()
.WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri(elasticUri))
{
MinimumLogEventLevel = LogEventLevel.Verbose,
AutoRegisterTemplate = true,
})
.CreateLogger();
}
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.Configure<IISServerOptions>(options =>
{
options.AutomaticAuthentication = false;
});
services.Configure<ApiBehaviorOptions>(options =>
{
//To handle ModelState Errors manually as ApiController attribute handles those automatically
//and return its own response.
options.SuppressModelStateInvalidFilter = true;
});
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});
services.AddControllers(options =>
{
//To accept browser headers.
options.RespectBrowserAcceptHeader = true;
}).
AddNewtonsoftJson(options =>
{
// Use the default property (Pascal) casing
options.SerializerSettings.ContractResolver = new DefaultContractResolver();
options.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
}).
AddJsonOptions(options =>
{
//Not applying any property naming policy
options.JsonSerializerOptions.PropertyNamingPolicy = null;
options.JsonSerializerOptions.IgnoreNullValues = true;
}).
AddXmlSerializerFormatters().
AddXmlDataContractSerializerFormatters();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
app.UseCors("CorsPolicy");
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
//Configuring serilog
loggerFactory.AddSerilog();
}
}
I tried testing the same API with the OPTIONS method from POSTMAN. It is also giving the Http Status Code as 405. But when trying to access the same request using the POST method, I received the response successfully.
Is there anything wrong with the above code or something wrong with the order of middlewares being called in Configure().

Try to add extension method and modifying your startup class:
Extension method:
public static void AddApplicationError(this HttpResponse response, string
message)
{
response.Headers.Add("Application-Error", message);
response.Headers.Add("Access-Control-Expose-Headers", "Application-Error");
response.Headers.Add("Access-Control-Allow-Origin", "*");
}
Startup.cs :
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler(builder =>
{
builder.Run(async context =>
{
context.Response.StatusCode = (int)
HttpStatusCode.InternalServerError;
var error = context.Features.Get<IExceptionHandlerFeature>();
if (error != null)
{
context.Response.AddApplicationError(error.Error.Message);
await context.Response.WriteAsync(error.Error.Message);
}
});
});
}
P.S. in my case I had scenario also returning 405 status error, cause was, similar action methods I used and there are conflicted
For ex:
[HttpGet]
public ActionResult GetAllEmployees()
[HttpGet]
public ActionResult GetCustomers()
Hope this will help at least to show exact error message

You need to add Cors in Startup.cs file under your web api project
add this variable in Startup.cs
readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
add services.AddCors before services.AddControllers() in the method ConfigureServices in file Startup.cs:
services.AddCors(options =>
{
options.AddPolicy(MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://localhost:4000",
"http://www.yourdomain.com")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
services.AddControllers();
*** You can pass only * to allow all instead of passing http://localhost:4000","http://www.yourdomain.com in the WithOrigins method
add app.UseCors before app.UseAuthentication() in the method Configure in file Startup.cs:
app.UseCors(MyAllowSpecificOrigins);
Check this Microsoft help

Try this:
app.UseCors(policy =>
policy.WithOrigins("https://localhost:PORT", "https://localhost:PORT")
.AllowAnyMethod()
.WithHeaders(HeaderNames.ContentType)
);

Related

Asp.Net Core 6 Scoped Filter inject UserManager

I'm working on linking Twilio Verify into an Asp.Net Core web site. I'm pretty sure I have to figure out how to access UserManager in the filter (constructor). But, I don't know how to access it.
My VerifyFilter:
public class VerifyFilter : IAsyncResourceFilter
{
private readonly UserManager<ApplicationUser> _manager;
public VerifyFilter(UserManager<ApplicationUser> manager)
{ _manager = manager; }
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{ // cut just to make listing a bit shorter }
}
My program.cs file currently looks like this:
builder.Services.AddScoped<VerifyFilter>();
What I don't know is how I get the UserManager so I can pass it in.
I have another scoped right above it and I had to do this to get Verification to work.
Configuration.Twilio twilio = new Configuration.Twilio();
builder.Services.AddScoped<IVerification>(t => new Verification(twilio));
So I'm sure it's just a matter of figuring out how to get UserManager to pass in as a constructor, but with MinimalAPI and .Net Core 6.0, I don't know where it is at.
My entire program.cs file:
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using test4.Data;
using test4.Filters;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services
.AddDefaultIdentity<IdentityUser>(
options =>
{
// These will get updated to production versions later.
options.SignIn.RequireConfirmedAccount = true;
options.Password.RequiredLength = 1;
})
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddScoped<VerifyFilter>();
builder.Services.AddControllers(op =>
{
op.Filters.Add<VerifyFilter>();
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Any suggestions?
Thanks,
Nick
You could try to register the filter via the following code:
//register the Identity service
//register the custom filters.
builder.Services.AddControllers(op =>
{
op.Filters.Add<VerifyFilter>();
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

CORS Error: No 'Access-Control-Allow-Origin' header is present on the requested resource

I am using .Net Core 3. I have programmed an API being called by a SPA hosted in a .Net Core Web Project.
For one POST action in the API, I get a CORS error in Chrome as well as Firefox while for another POST action in the same controller of the API, everything works fine.
The error that I get is
Access to fetch at 'https://subdomain1.domain1.com:50003/api/v1/projects/project' from origin
'https://subdomain2.domain2.com:50002' 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.
In the API,
In the startup class, I have following
readonly string MyAllowSpecificOrigins = "AllowOrigin";
public void ConfigureServices(IServiceCollection services)
{
CorsPolicyBuilder builder = new CorsPolicyBuilder();
CorsPolicy policy = builder.WithOrigins("https://subdomain2.domain2.com:50002")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
.Build();
services.AddCors(options =>
{
options.AddPolicy(MyAllowSpecificOrigins, policy);
});
services.AddControllers();
services
.AddAuthentication("Bearer")
.AddJwtBearer(jwtOptions => {
jwtOptions.Authority = "https://subdomain.domain.com:50001";
jwtOptions.Audience = "portal-api";
jwtOptions.RequireHttpsMetadata = false;
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
var provider = new FileExtensionContentTypeProvider();
provider.Mappings[".hrc"] = "application/octet-stream";
provider.Mappings[".obj"] = "application/octet-stream";
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors(MyAllowSpecificOrigins);
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers().RequireCors(MyAllowSpecificOrigins);
});
app.UseMvc();
}
In the API controller, I have a POST action for which I don't get any CORS error
[HttpPost]
[Route("[action]")]
[ActionName("thumbnail")]
public async Task<IActionResult> thumbnail([FromBody]dataDTO model)
{
.
.
.
}
In the same API, I have another POST action, for which the browser gives the above mentioned CORS error
[HttpPost]
[Route("project")]
[ActionName("project")]
public async Task<IActionResult> projectAdd([FromBody]projectDTO project)
{
.
.
.
}
I have already tried moving the app.UseCors(MyAllowSpecificOrigins); to the top of configure function in startup.cs. It did not help.
I have also tried moving the app.UseCors(MyAllowSpecificOrigins); statement and changing its order in configure with no difference.
In firefox, I have also verified that there is a Origin header in the request to the action for which we are getting CORS error.

Redirect to HTTPS in Blazor

I have a blazor app.
I hosted it on server and have access with https.
But when i do redirect (in one controller), happens exception.
Startap.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseResponseCompression();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseMvc(routes =>
{
routes.MapRoute(name: "default", template: "{controller}/{action}/{id?}");
});
app.Map("/schedule", subdirApp =>
{
subdirApp.UseBlazor<Client.Startup>();
});
}
And method in controller
[HttpGet]
[Route("***")]
public IActionResult Return()
{
FileStream fs = new FileStream(_filePath, FileMode.Open);
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
List<ScheduleEntity> _list = (List<ScheduleEntity>)formatter.Deserialize(fs);
foreach (var x in _list)
Schedules.Add(x);
fs.Close();
return Redirect("~//schedule");
}
Exception
Please, help me
These API responses can be a bit misleading. Without seeing the rest of your code around the configuration of endpoints, I suspect this might be a CORS issue with the API.
Try adding the following code to the public void Configure(IApplicationBuilder app, IHostingEnvironment env) method in your API's Startup.cs class:
app.UseCors(opts => opts
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
);
The fetch response may be due to the request preflight being rejected.
Having said that, the first exception message is saying you're trying to load insecure content, so I'd also check your Blazor front-end app's configuration to see what the API client is requesting and ensure the API endpoint certificate is valid?

How to force re authentication between ASP Net Core 2.0 MVC web app and Azure AD

I have an ASP.Net Core MVC web application which uses Azure AD for authentication. I have just received a new requirement to force user to reauthenticate before entering some sensitive information (the button to enter this new information calls a controller action that initialises a new view model and returns a partial view into a bootstrap modal).
I have followed this article which provides a great guide for achieving this very requirement. I had to make some tweaks to get it to work with ASP.Net Core 2.0 which I think is right however my problems are as follows...
Adding the resource filter decoration "[RequireReauthentication(0)]" to my controller action works however passing the value 0 means the code never reaches the await.next() command inside the filter. If i change the parameter value to say 30 it works but seems very arbitrary. What should this value be?
The reauthentication works when calling a controller action that returns a full view. However when I call the action from an ajax request which returns a partial into a bootstrap modal it fails before loading the modal with
Response to preflight request doesn't pass access control check: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'https://localhost:44308' is therefore not allowed
access
This looks like a CORS issue but I don't know why it would work when going through the standard mvc process and not when being called from jquery. Adding
services.AddCors();
app.UseCors(builder =>
builder.WithOrigins("https://login.microsoftonline.com"));
to my startup file doesn't make any difference. What could be the issue here?
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Ommitted for clarity...
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddAzureAd(options => Configuration.Bind("AzureAd", options))
.AddCookie();
services.AddCors();
// Ommitted for clarity...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// Ommitted for clarity...
app.UseCors(builder => builder.WithOrigins("https://login.microsoftonline.com"));
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
AzureAdAuthenticationBuilderExtensions.cs
public static class AzureAdAuthenticationBuilderExtensions
{
public static AuthenticationBuilder AddAzureAd(this AuthenticationBuilder builder)
=> builder.AddAzureAd(_ => { });
public static AuthenticationBuilder AddAzureAd(this AuthenticationBuilder builder, Action<AzureAdOptions> configureOptions)
{
builder.Services.Configure(configureOptions);
builder.Services.AddSingleton<IConfigureOptions<OpenIdConnectOptions>, ConfigureAzureOptions>();
builder.AddOpenIdConnect(options =>
{
options.ClaimActions.Remove("auth_time");
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = RedirectToIdentityProvider
};
});
return builder;
}
private static Task RedirectToIdentityProvider(RedirectContext context)
{
// Force reauthentication for sensitive data if required
if (context.ShouldReauthenticate())
{
context.ProtocolMessage.MaxAge = "0"; // <time since last authentication or 0>;
}
else
{
context.Properties.RedirectUri = new PathString("/Account/SignedIn");
}
return Task.FromResult(0);
}
internal static bool ShouldReauthenticate(this RedirectContext context)
{
context.Properties.Items.TryGetValue("reauthenticate", out string reauthenticate);
bool shouldReauthenticate = false;
if (reauthenticate != null && !bool.TryParse(reauthenticate, out shouldReauthenticate))
{
throw new InvalidOperationException($"'{reauthenticate}' is an invalid boolean value");
}
return shouldReauthenticate;
}
// Ommitted for clarity...
}
RequireReauthenticationAttribute.cs
public class RequireReauthenticationAttribute : Attribute, IAsyncResourceFilter
{
private int _timeElapsedSinceLast;
public RequireReauthenticationAttribute(int timeElapsedSinceLast)
{
_timeElapsedSinceLast = timeElapsedSinceLast;
}
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{
var foundAuthTime = int.TryParse(context.HttpContext.User.FindFirst("auth_time")?.Value, out int authTime);
var ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (foundAuthTime && ts - authTime < _timeElapsedSinceLast)
{
await next();
}
else
{
var state = new Dictionary<string, string> { { "reauthenticate", "true" } };
await AuthenticationHttpContextExtensions.ChallengeAsync(context.HttpContext, OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties(state));
}
}
}
CreateNote.cs
[HttpGet]
[RequireReauthentication(0)]
public IActionResult CreateNote(int id)
{
TempData["IsCreate"] = true;
ViewData["PostAction"] = "CreateNote";
ViewData["PostRouteId"] = id;
var model = new NoteViewModel
{
ClientId = id
};
return PartialView("_Note", model);
}
Razor View (snippet)
<a asp-controller="Client" asp-action="CreateNote" asp-route-id="#ViewData["ClientId"]" id="client-note-get" data-ajax="true" data-ajax-method="get" data-ajax-update="#client-note-modal-content" data-ajax-mode="replace" data-ajax-success="ShowModal('#client-note-modal', null, null);" data-ajax-failure="AjaxFailure(xhr, status, error, false);"></a>
All help appreciated. Thanks
The CORS problem is not in your app.
Your AJAX call is trying to follow the authentication redirect to Azure AD,
which will not work.
What you can do instead is in your RedirectToIdentityProvider function, check if the request is an AJAX request.
If it is, make it return a 401 status code, no redirect.
Then your client-side JS needs to detect the status code, and issue a redirect that triggers the authentication.

How to ConfigureServices Authentication based on routes in ASP.NET Core 2.0

In ASP.NET Core 1.x I could use authentication methods in Configure but now in ASP.NET Core 2.0 I have to set everything in ConfigureServices and can't configure it in Configure method. For example
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication()
.AddCookie()
.AddXX();
}
and then in
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
....
app.UseAuthentication();
}
in the past, I could use something like
app.UseOpenIdConnectAuthentication();
and I can't configure it anymore like this.
so how I can use something like this now in ASP.NET Core 2.0?
app.Map(new PathString("/MyPath"), i => i.UseMyAuthMethod());
In 2.0, the best option to do per-route authentication is to use a custom IAuthenticationSchemeProvider:
public class CustomAuthenticationSchemeProvider : AuthenticationSchemeProvider
{
private readonly IHttpContextAccessor httpContextAccessor;
public CustomAuthenticationSchemeProvider(
IHttpContextAccessor httpContextAccessor,
IOptions<AuthenticationOptions> options)
: base(options)
{
this.httpContextAccessor = httpContextAccessor;
}
private async Task<AuthenticationScheme> GetRequestSchemeAsync()
{
var request = httpContextAccessor.HttpContext?.Request;
if (request == null)
{
throw new ArgumentNullException("The HTTP request cannot be retrieved.");
}
// For API requests, use authentication tokens.
if (request.Path.StartsWithSegments("/api"))
{
return await GetSchemeAsync(OAuthValidationDefaults.AuthenticationScheme);
}
// For the other requests, return null to let the base methods
// decide what's the best scheme based on the default schemes
// configured in the global authentication options.
return null;
}
public override async Task<AuthenticationScheme> GetDefaultAuthenticateSchemeAsync() =>
await GetRequestSchemeAsync() ??
await base.GetDefaultAuthenticateSchemeAsync();
public override async Task<AuthenticationScheme> GetDefaultChallengeSchemeAsync() =>
await GetRequestSchemeAsync() ??
await base.GetDefaultChallengeSchemeAsync();
public override async Task<AuthenticationScheme> GetDefaultForbidSchemeAsync() =>
await GetRequestSchemeAsync() ??
await base.GetDefaultForbidSchemeAsync();
public override async Task<AuthenticationScheme> GetDefaultSignInSchemeAsync() =>
await GetRequestSchemeAsync() ??
await base.GetDefaultSignInSchemeAsync();
public override async Task<AuthenticationScheme> GetDefaultSignOutSchemeAsync() =>
await GetRequestSchemeAsync() ??
await base.GetDefaultSignOutSchemeAsync();
}
Don't forget to register it in the DI container (ideally, as a singleton):
// IHttpContextAccessor is not registered by default
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IAuthenticationSchemeProvider, CustomAuthenticationSchemeProvider>();
The Microsoft docs say what to do if you want to use multiple authentication schemes in ASP.NET Core 2+:
The following example enables dynamic selection of schemes on a per
request basis. That is, how to mix cookies and API authentication:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
// For example, can foward any requests that start with /api
// to the api scheme.
options.ForwardDefaultSelector = ctx =>
ctx.Request.Path.StartsWithSegments("/api") ? "Api" : null;
})
.AddYourApiAuth("Api");
}
Example:
I had to implement a mixed-authentication solution in which I needed Cookie authentication for some requests and Token authentication for other requests. Here is what it looks like for me:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
// if URL path starts with "/api" then use Bearer authentication instead
options.ForwardDefaultSelector = httpContext => httpContext.Request.Path.StartsWithSegments("/api") ? JwtBearerDefaults.AuthenticationScheme : null;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, o =>
{
o.TokenValidationParameters.ValidateIssuerSigningKey = true;
o.TokenValidationParameters.IssuerSigningKey = symmetricKey;
o.TokenValidationParameters.ValidAudience = JwtSignInHandler.TokenAudience;
o.TokenValidationParameters.ValidIssuer = JwtSignInHandler.TokenIssuer;
});
where the JWT Bearer authentication is implemented as described in this answer.
Tips:
One of the biggest 'gotchas' for me was this: Even though the Cookies Policy forwards requests with URLs that start with "/api" to the Bearer policy, the cookie-authenticated users can still access those URLs if you're using the [Authorize] annotation. If you want those URLs to only be accessed through Bearer authentication, you must use the [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] annotation on the API Controllers/Actions.