Custom HTTP response headers on internal IdentityServer4 endpoints - authentication

I've been trying to add some custom HTTP response headers to the built-in IdentityServer endpoints like /connect/checksession with global filter approach depicted in the code snippet below (taken from Startup.cs ConfigureServices method):
services.AddMvc(options =>
{
options.EnableEndpointRouting = false;
options.Filters.Add(typeof(SecurityHeadersAttribute));
});
While the headers appear just fine on custom MVC endpoints like /AccountSelect and /Login the internal IdentityServer endpoints seem to ignore those altogether.
I was thinking whether the order of the registration in the startup overrides the global filters. In my case the code below is executed after AddMvc
services.AddIdentityServer(options =>
{
//code omitted for brevity
});

One option is to add your own middleware step in the request pipeline that will execute for every incoming request to your IdentityServer application.
Just add this code in Startup.Configure:
app.Use(async (context, next) =>
{
context.Response.OnStarting(() =>
{
context.Response.Headers.Add("MyMagic", "Header");
return Task.FromResult(0);
});
await next();
});

Related

Authorize attribute using WsFederation never executes annotated controller action

I am using WsFederation middleware to access an ADFS server for authentication. ADFS is given a specific endpoint to call back at the end of the conversation between the middleware and ADFS. If I don't provide an actual endpoint in my code (some action that responds to the route = callback endpoint), I get a 404. If I do implement an action at that endpoint, I get nothing useful (e.g. 'User' not set) and - whatever my action does at the end with respect to a response goes straight back to the user's browser. At no point was the action I decorated with [Authorize] executed.
From startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
// set up ADFS authentication
services.AddAuthentication(sharedOptions => {
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
}).AddWsFederation(options =>
{
options.MetadataAddress = "<adfs-server>/FederationMetadata/2007-06/FederationMetadata.xml";
options.Wtrealm = "<my-apps-server>/authviaadfs/auth-callback";
}).AddCookie("Cookies", o => { });
// set up custom authorization
services.AddAuthorization(options => { });
services.AddControllersWithViews();
services.AddRazorPages();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
//app.UseHsts();
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
From MyController:
[Authorize]
public IActionResult MyProtectedPage()
{
... code that never, ever, executes when decorated with [Authorize]
}
[Route("/authviaadfs/auth-callback")]
public IActionResult AuthCallback()
{
... code that executes after I log in via ADFS
... response that returns to the original caller of "MyProtectedPage"
}
Can anyone tell me what I'm doing wrong? I've followed the recipe from half-dozen different Googled websites that say "this is how you authenticate to ADFS" (all slightly different, but the gist is the same, including setting only the options for 'MetadataAddress' and 'WtRealm').
Okay, I figured it out - another example of "Googled examples that are useless in the real world" and "bad/missing documentation of how to do something".
There is another "option" to set in addition to "MetadataAddress" and "Wtrealm" if you want to use an endpoint other than "/" - an option called "CallbackPath". This tells the middleware what route in your code to "swallow and process"; if you don't set it (as I didn't originally, following the recipes) then your middleware doesn't know which request to intercept. So as far as the sample code I provided in the question, after setting options.MetadataAddress and options.Wtrealm you would set the following:
options.CallbackPath = "/authviaadfs/auth-callback";
That tells the middleware to, inside the request pipeline, to intercept any calls to "/authviaadfs/auth-callback" and use the request and headers to finish the authentication and then, effectively, give control to your protected controller action. The middleware creates a correlation cookie that it sends along with the first redirect to your ADFS server, then uses that cookie in the "/authviaadfs/auth-callback" to match the return call from ADFS to the proper request context that was being authenticated.

Disable HTTP Options method in ASP.Net Core 3.1

We have a requirement of disabling the HTTP OPTIONS method in an ASPNET Core Web application due as a part of security fixes. How can we disable the HTTP OPTIONS method in ASP.Net core 3.1 API?
Here is a demo with middleware:
Add this to your startup Configure:
app.Use(async (context, next) =>
{
// Do work that doesn't write to the Response.
if (context.Request.Method=="OPTIONS")
{
context.Response.StatusCode = 405;
return;
}
await next.Invoke();
// Do logging or other work that doesn't write to the Response.
});
result:
Or you can apply
[HttpGet]
[HttpPost]
[HttpPut]
...
on your action method in controller.Here is an official document about the Http Verbs.

CORS error with Aurelia calling .NET core API 2.0

I am getting a CORS error and I don't know how to fix it. I have an Aurelia app, calling a .NET core 2.0 API using aurelia-fetch-client. I am getting the following error:
Failed to load http://localhost:58289/api/info: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8080' is therefore not allowed access. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
TypeError: Failed to fetch
at applyInterceptors (webpack-internal:///./node_modules/aurelia-fetch-client/dist/native-modules/aurelia-fetch-client.js:428:14)
at processResponse (webpack-internal:///./node_modules/aurelia-fetch-client/dist/native-modules/aurelia-fetch-client.js:411:10)
at eval (webpack-internal:///./node_modules/aurelia-fetch-client/dist/native-modules/aurelia-fetch-client.js:299:14)
From previous event:
at HttpClient.eval (webpack-internal:///./node_modules/aurelia-fetch-client/dist/native-modules/aurelia-fetch-client.js:287:61)
at HttpClient.fetch (webpack-internal:///./node_modules/aurelia-fetch-client/dist/native-modules/aurelia-fetch-client.js:273:21)
at App.callApi (webpack-internal:///app:42:25)
at CallScope.evaluate (webpack-internal:///./node_modules/aurelia-binding/dist/native-modules/aurelia-binding.js:1578:19)
at Listener.callSource (webpack-internal:///./node_modules/aurelia-binding/dist/native-modules/aurelia-binding.js:5279:40)
at Listener.handleEvent (webpack-internal:///./node_modules/aurelia-binding/dist/native-modules/aurelia-binding.js:5288:10)
at HTMLDocument.handleDelegatedEvent (webpack-internal:///./node_modules/aurelia-binding/dist/native-modules/aurelia-binding.js:3363:20)
Please find my code below.
aurelia-fetch-client configuration:
const http = new HttpClient().configure(config => {
config
.withBaseUrl(environment.apiBaseUrl)
.withDefaults({
headers: {
'Content-Type': 'application/json'
}
})
.withInterceptor({
request(request: Request) {
var token = localStorage.getItem('access_token')
request.headers.append('Authorization', 'Bearer ' + token)
return request;
},
responseError(error){
return error;
}
});
});
aurelia.container.registerInstance(HttpClient, http);
Call the API:
callApi(){
this.httpClient.fetch("/info")
.then(response => console.log(response));
}
API startup configuration:
public void ConfigureServices(IServiceCollection services)
{
string domain = $"https://{Configuration["Auth0:Domain"]}/";
var allowedCors = Configuration["CorsSite"];
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = domain;
options.Audience = Configuration["Auth0:ApiIdentifier"];
});
services.AddCors(options => options.AddPolicy("AllowSpecificOrigin", `builder => {`
builder.AllowAnyOrigin().AllowAnyMethod(); }));
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.UseCors("AllowSpecificOrigin");
app.UseAuthentication();
app.UseMvc();
}
Controller:
[Produces("application/json")]
[Route("api")]
public class InfoController : Controller
{
// GET api/values
[HttpGet]
[Route("Info")]
public IActionResult Get()
{
return Ok("Api V1.0");
}
[Route("authorizedInfo")]
[Authorize]
[HttpGet]
public IActionResult GetAuthorized()
{
return Ok("Authorized Api V1.0");
}
}
Please ignore the authorisation bit for now. I am only trying to hit the unauthorised API endpoint in localhost, but I am stuck. How can I fix my problem?
To do this start with registering CORS functionality in ConfigureServices() of Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// Add service and create Policy with options
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials() );
});
services.AddMvc();
}
The AddCors() call above adds the CORS features to ASP.NET and creates a custom policy that can be reused in the application by name. There are other ways to do essentially the same thing by explicitly adding a policy builder in the configuration step but to me this seems cleanest - define one or more policies up front and then apply it.
Once the policy has been defined it can be applied.
You can apply the policy globally to every request in the application by call app.useCors() in the Configure() method of Startup:
public void Configure(IApplicationBuilder app)
{
// ...
// global policy - assign here or on each controller
app.UseCors("CorsPolicy");
// ...
// IMPORTANT: Make sure UseCors() is called BEFORE this
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
or you can apply the policy to individual controllers:
[EnableCors("CorsPolicy")]
[ApiExceptionFilter]
public class AlbumViewerApiController : Controller
Thank You
The answer in the following link fixed my issue.
Web API 2 CORS IIS Express Debug and No Access-Control-Allow-Origin header
It appears that if there is no origin header in the request the server will not respond with the corresponding Access-Control-Allow-Origin response. Also with aurelia-fetch-client defaults I would have expected to have the origin header added by default.

IdentityServer4- Challenge ALL requests to API not just [Authorize]

I have an ASP.Net Core 2 API using IdentityServer4. I would like challenge ALL requests to the server and invoke the login redirect if the user is not authenticated, calling back to a specific URL after authentication.
The default is to invoke the login redirect only when an unauthenticated user requests a resource protected by the [Authorize] attribute. This will not work in my use case.
Basically, I want the functional equivalent of an [Authorize] attribute for the whole application not just specific controllers.
What is the easiest way to do this? Is there a setting I can use when configuring the services in Startup.cs (services.AddAuthentication)? Or through custom middleware right after app.UseAuthentication()?
I tried the following custom middleware but it says a handler is not configured.
ConfigureServices
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = "https://localhost:4000";
options.ApiName = "myapi";
});
Configure
app.UseAuthentication();
app.Use(async (context, next) =>
{
if (!context.User.Identity.IsAuthenticated)
{
await context.ChallengeAsync(IdentityServerAuthenticationDefaults.AuthenticationScheme, new AuthenticationProperties {RedirectUri= "https://localhost:5000/" });
}
else { await next.Invoke(); }
});
app.UseMvc();
For configuring [Authorize] for the whole controllers, you could try AuthorizeFilter like below
services.AddMvc(config => {
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
For redirecting, you could try configure UserInteraction.LoginUrl
services.AddIdentityServer(opt => {
opt.UserInteraction.LoginUrl = "/Identity/Account/LogIn";
})
.AddDeveloperSigningCredential()
.AddInMemoryPersistedGrants()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddAspNetIdentity<IdentityUser>();

How to set deaful Authorization in .net core 2

I am using a custom requirement like this:
services.AddAuthorization(options => {
options.AddPolicy("MyAuthorizationRequirement",policy => {
policy.Requirements.Add(new MyRequirement());
});
});
I want to be able to set an [Authorize] attribute on my controller and use a custom authorization method.
but with the custom requirement i set i have to use [Authrization("MyAuthorizationRequirement")]
how can I set some sort of default to use just a single attribute ?
Let me try to explain what is required here.
What services.AddAuthorization does is, it tries to configure authorization. Before configuring authorization first you need to configure authentication. For that in the ConfigureServices method, you can do something like this.
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Audience = "http://localhost:5001/";
options.Authority = "http://localhost:5000/";
});
Now you can configure,
services
.AddAuthorization(options =>
{
options.AddPolicy("PolicyName", policy => policy.RequireClaim("SomeClaimName"));
// you can configure your requirement above in various ways, you might want to check those out
});
And finally on the Configure method,
app.UseAuthentication();
So now in the controller,
// [Authorize("PolicyName")]
// If you have above in your controllers/actions, those will only be accessible to users who has "SomeClaimName"
// [Authorize]
// Those who are authenticated, can access these endpoints
You can create a new class which inherit from AuthorizeAttribute
public class MyAuthorizationAttribute : AuthorizeAttribute
{
public MyAuthorizationAttribute()
: base("MyAuthorizationRequirement")
{
}
}
and use [MyAuthorization] on your controllers.