How to enable CORS in ASP.NET Core Preflight - asp.net

I’m working with ASP.NET Core. Created the API Controller to handle Create, Get, Delete etc. I want to be able to call he APIs from Angualr2 and finally Nativescript as an APP. Set the Cors in ConfigureServices of startup as follows:
#region Cors
//allows Cors for API
//NB: must procede any endpoints
services.AddCors(o => o.AddPolicy("AllowAll", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
.SetPreflightMaxAge(TimeSpan.FromSeconds(2520))
.Build();
}));
// Apply as default to all controllers. API etc
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(new CorsAuthorizationFilterFactory("AllowAll"));
});
#endregion
Then referenced posy in the controller and actions:
[Produces("application/json")]
[Route("api/recipes")]
[EnableCors("AllowAll")]
and also
// DELETE: api/5
[HttpDelete("{id}")]
[EnableCors("AllowAll")]
public async Task<IActionResult> DeleteRecipe([FromRoute] int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Recipe.Models.Recipe recipe = await _context.Recipe.SingleOrDefaultAsync(m => m.RecipeID == id);
if (recipe == null)
{
return NotFound();
}
_context.Recipe.Remove(recipe);
await _context.SaveChangesAsync();
return Ok(recipe);
}
Everything works save for delete method. I get 405 method not allowed. This however works if hosting server and client are on the same domain (i.e. Localhost testing). The issue as I understand it is the preflight checks. What am I missing. Please note this is .Net Core and different from prev. versions of asp.net where u can simply create in web.config. Which incidentally I tried creating double entries in header

Related

Updating CORS Policy Origins During RunTime on All Servers

I am trying to update the CORS Policy Origins during runtime. However, It is only updating on one server and not on all the servers. My web application is hosted on 4 servers and I want to update it on all 4 servers.
Here is the Startup.cs Code:
// Configure CORS
services.AddCors(c =>
{
c.AddPolicy("CorsPolicy", options => options
.SetIsOriginAllowedToAllowWildcardSubdomains()
.WithOrigins(origins)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
);
});
Code To Update the Origins During runtime :
public class CorsPolicyAccessor : ICorsPolicyAccessor
{
private readonly ICorsPolicyProvider _cors;
private readonly string _corsPolicy;
public CorsPolicyAccessor(ICorsPolicyProvider cors)
{
_corsPolicy = "CorsPolicy";
_cors = cors;
}
public async Task<CorsPolicy> GetPolicy(HttpContext context)
{
return await _cors.GetPolicyAsync(context,_corsPolicy);
}
public async Task AddDomainToPolicy(string origin, HttpContext context)
{
//_cors.GetPolicyAsync(context, _corsPolicy);
(await _cors.GetPolicyAsync(context, _corsPolicy)).Origins.Add(origin);
}
I have tried searching a lot and have tried corsoptions as wel. But No Luck yet.

SignalR gives 404 when using WebSockets or ServerSentEvents for some users but not for others

SignalR gives me 404 when trying to connect for some users. URLs are the same except for access_token.
It is stable reproducible per user (I mean that some users are stable OK, some users are stable 404).
access_token parsed jwt diff (left is OK user, right gets 404):
I did a trace level of logs and have next:
For the OK user:
For the user that gets 404:
Note: URLs under black squares are the same.
Front End is Angular 9 with package "#microsoft/signalr": "^3.1.8", and here's the code that builds the connection:
private buildHub(): HubConnection {
console.log(this.authService.accessToken);
let builder = new HubConnectionBuilder()
.withAutomaticReconnect()
.configureLogging(LogLevel.Information)
.withUrl('ws/notificationHub', {
accessTokenFactory: () => this.authService.accessToken
});
if (this.debugMode) {
builder = builder.configureLogging(LogLevel.Trace);
}
return builder.build();
}
Backend is using next code in Startup for configuring signalR hub:
In public void ConfigureServices(IServiceCollection services):
services.AddSignalR()
.AddJsonProtocol(options =>
{
options.PayloadSerializerSettings.ContractResolver = new DefaultContractResolver();
});
In public void Configure(IApplicationBuilder app, IHostingEnvironment env):
app.UseSignalR(route =>
{
route.MapHub<NotificationHub>("/ws/notificationHub");
});
Also we use custom authentication, so we have Authorize attribute for the Hub class:
[Authorize]
public class NotificationHub: Hub<INotificationHubClient>
and this code in public void ConfigureServices(IServiceCollection services):
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = identityServerSettings.Url;
options.Audience = identityServerSettings.ApiScopeName;
options.RequireHttpsMetadata = identityServerSettings.RequireHttpsMetadata;
options.Events = new Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/ws"))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
Unfortunately, I don't have the full access to the environment where it is reproducible, but I can request to see any settings or try to make some changes.
What else can I try to troubleshoot the issue?
UPDATE: negotiate is fine for both users.
I had this issue recently, after the size of my JWT increased. I found that in my case the 404 error was being thrown by IIS because the query string exceeded the limit of 2048. After increasing the query string max length, my issue was resolved.

Getting information from the Bearer Token in .net core api to do additional authorization on an endpoint

I have an angular client that authorizes users via Auth0 and then uses the token from Auth0 to consume an API that is built in .net core. The authentication in Startup.cs is
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = "removed";
options.Audience = "removed";
});
I have an endpoint like this
[Authorize]
[HttpGet("preview/{questionId}")]
[...]
public ActionResult<Question> GetQuestionPreviewById(string questionId)
{
...
return Ok(model);
}
This all works fine. However, I would like to put additional authorization logic on this endpoint and that would require knowing who the user is - that information is available on the token (the sub field) but I:
1. Don't know how to get to that from the endpoint
2. Don't think the endpoint is the correct place to do this.
My gut tells me I should have another (in angular language) guard that can be added to the endpoint, something like
[Authorize]
[AuthorizeOwnershipOfRequestedObject]
But I don't know how to do this in .net.
How do I go about using the provided token to add additional authorization on an endpoint?
1) you need first to assign claims to a specific user
2) In configure services add authorization with option to include policies
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy("EmployeeOnly", policy =>
policy.RequireClaim("EmployeeNumber"));
});
}
3) You then apply the policy using the Policy property on the AuthorizeAttribute attribute to specify the policy name
[Authorize(Policy = "EmployeeOnly")]
public IActionResult VacationBalance()
{
return View();
}

Unable to run .net core 2.2 api application :No authenticationScheme was specified, and there was no DefaultChallengeScheme found

We are integrating Azure Active Directory with React web application and backend would be .net core 2.2 API.
Current Status: we could able to add app registration for React web application into Azure AD. With that, we could able to do authentication and getting Bearer token after successful authentication on react web app.
Challenges I’m facing: Since authentication is done, I would like to perform Authorization at Backend .net core API with use of Bearer token. However, I couldn’t get success as I keep getting the following error.
InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found. Could anyone help to resolve this issue?
Code snippet from .net core api project:
in startup file: ConfigureServices(IServiceCollection services) method.
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddAuthorization(options =>
{
options.AddPolicy("RequireAdministratorRole",
policy =>
{
policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
policy.AddRequirements(new AdminRequirement());
policy.RequireAuthenticatedUser();
policy.RequireRole("Admin");
});
});
services.AddSingleton<IAuthorizationHandler, AdminHandler>();
In Configure(IApplicationBuilder app, IHostingEnvironment env):
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAuthentication();
app.UseMvc();
And AuthenticationHandler:
public class AdminHandler : AuthorizationHandler<AdminRequirement>, IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AdminRequirement requirement)
{
//Check user claims for Role
if (context.User.HasClaim(ClaimTypes.Role, "Admin"))
{
context.Succeed(requirement);
}
return Task.FromResult(0);
}
}
If you need to specify the schema, maybe due to different kind of authentication you should add in the Configure() method this code:
app.Use(async (context, next) =>
{
IEnumerable<string> schemes = GetSchemesForRequest(context);
foreach (string scheme in schemes)
{
AuthenticateResult result = await context.AuthenticateAsync(scheme);
if (!result.Succeeded)
continue;
context.User = result.Principal;
break;
}
await next();
});
Then in your method:
IEnumerable<string> GetSchemesForRequest(HttpContext context)
you can think what is the proper scheme for that request.

OpenIddict - hosting auth server and web api resource in same project

I want to implement an OpenIdConnect/Oauth2 server using OpenIddict in order to secure a .NET core API app. Most examples I have seen implement these as separate projects.
The client app is a SPA and we are using implicit flow.
I have based my solution on the code shown in the OpenIddict samples here:
https://github.com/openiddict/openiddict-samples
For the project I am working on it would ideally have the Auth server and API to use the same port and be in the same project. ( One of the customer's requirements is that they don't want another server to configure since they own the API resource and it will be on the same server)
I have configured OpenIddict and combined it with our API project. Almost everything works correctly - the API endpoints are protected with the [Authorize] attribute and prevent access to protected API end points. However, when the API resource is protected, instead of returning a 401 Unauthorized HTTP status code, the returned result is the HTML Login page of the Auth server itself.
Here is the relevant setup code in my Startup.cs file:
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseApplicationInsightsRequestTelemetry();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseApplicationInsightsExceptionTelemetry();
app.UseStaticFiles();
app.UseIdentity();
app.UseCors("AllowAll");
//app.UseCors(builder =>
//{
// builder.AllowAnyOrigin();//)WithOrigins("http://localhost:9000");
// builder.WithMethods("GET","POST", "PUT", "DELETE", "OPTIONS");
// builder.WithHeaders("Authorization");
//});
app.UseWhen(context => !context.Request.Path.StartsWithSegments("/api"), branch =>
{
branch.UseIdentity();
});
app.UseWhen(context => context.Request.Path.StartsWithSegments("/api"), branch =>
{
branch.UseOAuthValidation();
});
app.UseOpenIddict();
#region Adding resource config here (api)
// Add external authentication middleware below. To configure them please see http://go.microsoft.com/fwlink/?LinkID=532715
app.UseOAuthIntrospection(options =>
{
options.AutomaticAuthenticate = true;
options.AutomaticChallenge = true;
options.Authority = "http://localhost:5000";
options.Audiences.Add("resource-server-1");
options.ClientId = "resource-server-1";
options.ClientSecret = "846B62D0-DEF9-4215-A99D-86E6B8DAB342";
});
//app.UseCors(builder => {
// builder.WithOrigins("http://localhost:9000");
// builder.WithMethods("GET");
// builder.WithHeaders("Authorization");
//});
#endregion
app.UseMvcWithDefaultRoute();
// Seed the database with the sample applications.
// Note: in a real world application, this step should be part of a setup script.
InitializeAsync(app.ApplicationServices, CancellationToken.None).GetAwaiter().GetResult();
}
private async Task InitializeAsync(IServiceProvider services, CancellationToken cancellationToken)
{
// Create a new service scope to ensure the database context is correctly disposed when this methods returns.
using (var scope = services.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
//await context.Database.EnsureCreatedAsync();
var manager = scope.ServiceProvider.GetRequiredService<OpenIddictApplicationManager<OpenIddictApplication>>();
if (await manager.FindByClientIdAsync("MySPA", cancellationToken) == null)
{
var application = new OpenIddictApplication
{
ClientId = "MySPA",
DisplayName = "MySPA",
LogoutRedirectUri = "http://localhost:9000/signout-oidc",
RedirectUri = "http://localhost:9000/signin-oidc"
};
await manager.CreateAsync(application, cancellationToken);
}
if (await manager.FindByClientIdAsync("resource-server-1", cancellationToken) == null)
{
var application = new OpenIddictApplication
{
ClientId = "resource-server-1"
};
await manager.CreateAsync(application, "846B62D0-DEF9-4215-A99D-86E6B8DAB342", cancellationToken);
}
}
}
Not sure how to implement these both side by side in the same project. As mentioned it all "works" except the API is returning the HTML login page and not a desired HTTP status
app.UseIdentity(); is present twice in your pipeline, which defeats the whole purpose of using branch.UseIdentity() in a app.UseWhen() branching builder (i.e making sure the cookies middleware registered by Identity are not invoked for your API endpoints).
Remove the first occurrence and it should work.
You set the AutomaticChallenge to true. According to the documentation
this flag indicates that the middleware should redirect the browser to the LoginPath or AccessDeniedPath when authorization fails.
So by setting this to false it will not redirect to the login.

Resources