We have been looking a lot on StackOverflow for this but we cannot seem to have it working.
Our scenario is as following. We have server A and server B.
Server A is a web API that communicates with Server B which is also a web API.
Server B should ONLY serve http requests comming from server A and deny all other requests.
We are trying to force this by using cors
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy(name: "AllowSpecificOrigin", builder =>
{
builder.WithOrigins(new string[0]); // Empty list as a test.
});
});
...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider)
{
app.UseRouting();
app.UseCors("AllowSpecificOrigin");
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
...
}
But now in one of our controllers, there are two methods. One that requires CORS and one that doesn't.
[ApiController]
[Route("api/Server")]
public sealed class ServerController : ControllerBase
{
[HttpGet]
[Route("ServerCheck")]
[DisableCors]
public IActionResult ServerCheck()
{
return Ok(true);
}
[HttpGet]
[Route("Version")]
[EnableCors("AllowSpecificOrigin")]
public IActionResult Version()
{
return Ok(GetType().Assembly.GetName().Version.ToString());
}
}
If I know issue a request through Postman to both methods, both methods supply an answer. But since we did not allow any origin, how come our request is served in the controller?
Is CORS the wrong way to tackle this issue?
What whould be the best way?
Or is our configuration simply wrong?
Nowhere in our appsettings.json or appsettings.Development.json is there a line 'AllowedHosts'.
Have you considered an IP Address allow list? Or an API Key or some other form of authentication?
It seems as if you are trying to leverage CORS for access control which is not it's purpose. The Mozilla Docs are pretty good, but the short version is that CORS is a way to safely get around the browser same origin restrictions and you should not expect it to work in the same way using Postman or any other non-browser client.
It is also worth considering that every header can be controlled by an attacker and so shouldn't be trusted and any credentials should be transmitted only via https
You could create a custom authorization filter which validates that the host is allowed via an authorization filter
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
public class ValidateDomainAttribute : Attribute, IAuthorizationFilter
{
private IEnumerable<string> AllowedDomains { get; }
public ValidateDomainAttribute(params string[] domains){
AllowedDomains = domains;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var host = context.HttpContext.Request.Host;
if (!AllowedDomains.Contains(host, StringComparer.OrdinalIgnoreCase))
{
context.Result = new BadRequestObjectResult("Domain is not allowed !");
}
}
}
And to apply this globally for all controllers and actions by registering this attribute in the StartUp class:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options =>
{
options.Filters.Add(new ValidateDomainAttribute("your-allowed-domain-1", "your-allowed-domain-2");
)};
// rest of code
}
}
More about filter attributes can be found here: https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-5.0
Related
i've the following problem: i've a web service application that uses ServiceStack. I'd like to register as base path "/api", but even if I set DefaultRedirectPath to "/api/metadata", when i start the app it won't redirect automatically (if i type "/api/metadata" all works)
Can anyone help me? Here's my code inside AppHost
public override void Configure(Container container)
{
SetConfig(new HostConfig
{
DefaultRedirectPath = "/api/metadata"
});
}
public class Startup
{
public IConfiguration Configuration { get; }
public Startup()
{
Configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
}
public void ConfigureServices(IServiceCollection services)
{
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseServiceStack(new AppHost
{
PathBase = "/api",
AppSettings = new NetCoreAppSettings(Configuration)
});
}
}
Thanks in advance and sorry for my english
Firstly I'd consider not using an /api PathBase which would disable the new /api route. E.g. if you didn't have a /api PathBase you would automatically be able to call a Hello API from /api/Hello.
But if you you still want to host ServiceStack at a custom /api path know that this is the path that ServiceStack will be mounted at, i.e. from where ServiceStack will be able to receive any requests.
Which also means you should just use /metadata which will redirect from where ServiceStack is mounted at, so if you had:
public class AppHost : AppHostBase
{
public AppHost() : base("MyApp", typeof(MyServices).Assembly) {}
public override void Configure(Container container)
{
SetConfig(new HostConfig {
DefaultRedirectPath = "/metadata"
});
}
}
Then calling https://localhost:5001/api will redirect to https://localhost:5001/api/metadata.
ServiceStack can only see requests from /api where it's mounted at, so if you wanted to redirect the / route to the metadata page you would need to register a custom ASP.NET Core handler to do it, e.g:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseServiceStack(new AppHost {
PathBase = "/api",
});
app.UseRouting();
app.UseEndpoints(endpoints => {
endpoints.MapGet("/", async context =>
context.Response.Redirect("/api/metadata"));
});
}
Note: you no longer need to set NetCoreAppSettings() which is populated by default
What is a recommended way to allow a restful endpoint api or even a controller to be exposed in Development but upon publishing to other environments it is not made available?
There is no built-in way to do this. You'd have to do something like inject IHostingEnvironment into your controller and then do a check like the following in your action:
if (!env.IsDevelopment())
{
return NotFound();
}
That would then give the appearance that the route didn't actually exist outside of the development environment. If you're going to be doing this enough, it would probably actually be better to create a custom resource filter that you could apply:
public class DevelopmentOnlyAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var env = context.HttpContext.RequestServices.GetService<IHostingEnvironment>();
if (!env.IsDevelopment())
{
context.Result = new NotFoundResult();
}
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
Which you could then apply to the relevant actions like:
[DevelopmentOnly]
public IActionResult Foo()
I'm trying to do ASP.NET Core 2 api with windows authentication. I need some unusual authorization requirements so I decided to create my own requirement for a policy.
public void ConfigureServices(IServiceCollection services)
{
(...)
services.AddAuthorization(options =>
{
options.AddPolicy("MyPolicy", policy => policy.AddRequirements(new MyRequirement())
);
});
}
My requirement:
public class MyRequirement: IAuthorizationRequirement
{
(...)
}
Handler for it:
public class MyHandler: AuthorizationHandler<MyRequirement>
{
public IService userService;
public MyHandler(IService service)
{
this.service = service;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MyRequirement requirement)
{
(...)
return Task.CompletedTask;
}
}
And obviously, Authorize attribute for my method:
[Authorize(Policy = "MyPolicy")]
public IEnumerable<string> GetAll()
{
(...)
}
But when I try to access such a method I get:
InvalidOperationException: No authenticationScheme was specified, and
there was no DefaultForbidScheme found.
I wasted a lot of time trying to fix it. Why is it happening and how can I get it working?
Everything happens locally, on IISExpress.
I am trying to use Spring Cloud Zuul as an api/authentication gateway. I have successfully implemented bearer token authorization for my service behind zuul and I successfully have Zuul forwarding to my form login and routing back to my application, but I cannot get Zuul to pass the bearer token to the service.
My Zuul configuration is as follows:
#EnableEurekaClient
#EnableZuulProxy
#SpringBootApplication
#RestController
public class Application { ... }
My service configuration is as follows:
#Profile("oauth")
#Configuration
#EnableResourceServer
#EnableWebSecurity
public static class InternalApiGatewayConfig extends ResourceServerConfigurerAdapter {
When my Angular app tries to access my service through zuul, I get
{"error":"unauthorized","error_description":"Full authentication is required to access this resource"}
I have managed to work around this issue by putting the following code in a ZuulFilter, but it doesn't seem right:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)authentication.getDetails();
String tokenValue = details.getTokenValue();
ctx.addZuulRequestHeader("Authorization", "bearer " + tokenValue);
My understanding is that Zuul should automatically send the bearer token value. What am I missing?
So I've figured out the answer to my own question, and it was painfully simple. My project imported spring-security-oauth2. I simply needed to add a dependency on spring-cloud-security as well. With that, I did not have to implement a ZuulFilter at all.
Btw this is the solution that works without spring-cloud-security
#Component
public class TokenRelayFilter extends ZuulFilter {
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
#SuppressWarnings("unchecked")
Set<String> headers = (Set<String>) ctx.get("ignoredHeaders");
// JWT tokens should be relayed to the resource servers
headers.remove("authorization");
return null;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 10000;
}
}
I'm diving into the deployment of websites for the first time. I'm making Cross Origin Request (CORS) in the web api controller from an Angular controller on the client. For development, I've set the EnableCors attribute on the Web Api controller, but obviously, that's pointing to a site on my local machine. I'm trying to figure out how to easily transform that setting as I move it to a hosted production site.
Enable CORS For All Domains
You first option is to enable CORS for all domains. This might not be the most secure option if, for example, you know that your API will be accessed only from a pre-defined set of web sites (e.g. your Angular app). But it some cases it is OK to enable CORS globally.
You can do it from WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Enable CORS globally for all routes
var enableCorsAttribute = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(enableCorsAttribute);
// Other configurations
}
}
Or enable CORS support in config and then use EnableCors attribute on specific controllers/actions:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.EnableCors();
// Other configurations
}
}
public class ValuesController : ApiController
{
[HttpGet]
[Route("api/values")]
[EnableCors(origins: "http://www.example.com", headers: "*", methods: "*")]
public string[] GetValue()
{
}
}
Enable CORS From Azure Portal
If host in Azure, I think Web Apps now allow you to enable CORS support and to specify allowed domains right from the Azure Portal:
Enable CORS Based on App Settings
Another option is to enable CORS for domains that can be configured from App Settings. This way you can change allowed domains for different API instances using web.config transforms, deployment token injection, or just Azure App Settings. This can be easily achieved by creating your own attribute that implements ICorsPolicyProvider interface:
// The implementation below supports only a single origin and
// doesn't allow you to specify allowed headers or request types.
// But it can be easily extended to support these scenarios as well.
public class EnableCorsWithConfigAttribute : Attribute, ICorsPolicyProvider
{
private readonly string configKey;
public EnableCorsWithConfigAttribute(string configKey)
{
this.configKey = configKey;
}
public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
var policy = new CorsPolicy
{
AllowAnyOrigin = false,
AllowAnyHeader = true,
AllowAnyMethod = true,
};
if (ConfigurationManager.AppSettings
.AllKeys
.Contains(configKey))
{
var origin = ConfigurationManager.AppSettings[configKey];
if (!origins.IsNullOrWhitespace())
{
policy.AllowAnyOrigin = origins.Equals("*");
if (!policy.AllowAnyOrigin) policy.Origins.Add(origin);
}
}
return Task.FromResult(policy);
}
}
Then you can use it as follows:
public class ValuesController : ApiController
{
[HttpGet]
[Route("api/values")]
[EnableCorsWithConfig("Application:Cors:AllowedOrigin")]
public string[] GetValue()
{
}
}