404 error when calling endpoints from swagger - http-status-code-404

I went accross a lot of different SO posts, without finding any that solved my problem, and I feel I am only missing a small thing.
I recently renamed my .net solution and manually added an Angular app in my project following this advice, and the controllers won't work again. I searched for any reluctant data that would still have the old name, but I found nothing like that.
There is a small particularity in my project, I have, in my Controllers folder an Administrators folder and a Clients folder.
Admin controllers are setup like this :
[Controller]
[Route("api/admin/[controller]/[action]")]
public class CareController : Controller
{
// GET requests
[HttpGet]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> GetAll()
{
...
}
...
}
And my client controllers are setup like this :
[Controller]
[Route("api/client/[controller]/[action]")]
public class ClientCareCategoryController : Controller
{
// GET
[HttpGet]
public async Task<IActionResult> GetAll()
{
...
}
}
Please note I deleted the constructor, [ProducesResponseType()], fields and namespace for clarity reasons. The namespaces use the new solution name.
In my Program.cs, I setup my application like this :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddSwaggerGen(config =>
{
config.SwaggerDoc("Backend", new OpenApiInfo { Title = "Backend", Version = "0.1"});
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/Backend/swagger.json", "Backend");
});
}
else
{
app.UseHttpsRedirection();
}
app.MapControllers();
app.Run();
The Spa related config is the exact same as the config specified in the issue I linked above.
I also checked my launchsettings.json file, and I also changed the solution name with the new one.
What I tried
Being helped by previous SO posts, here is what I tried without success using routing middleware and MapControllerRoute
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "client",
pattern: "api/client/{controller}/{action}");
endpoints.MapControllerRoute(
name: "admin",
pattern: "api/admin/{controller}/{action}");
});
In addition to that, I also tried removing "client" from one of the controllers, but it didn't work either.
Thanks a lot for any help. Please tell me if you need any other information.
EDIT :
The issue comes from the SPA. When I launch the app when commenting the app.UseSpa(), I can call my endpoints. But I have no idea why do I have this issue atm.

Related

Cross-Origin Request Blocked even though it responds with 200 ok status code

Folks I'm developing a fullstack React.js - ASP.NET Core 5 application. The backend is done (fully tested). Of course it includes a CORS policy to allow request from the client side, but when I'm trying to send a request from react using axios, axios throws a network error:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://localhost:5001/api/customers. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 200.
I see the server sends correct responses (I can even debugged the server) but axios stills failing. I only tried to solved it by including a proxy in package.json:
"proxy": "https://localhost:5001"
I'm going to include my app.js request code and startup.cs code, since it contains the CORS Policy:
Client
const fetchCustomers = async () => {
const customers = await axios.get(customersApiUrl);
console.log(customers);
setCustomers(customers);
setIsLoading(false);
};
Server
public class Startup
{
readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
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.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://localhost:3000/");
builder.AllowAnyHeader();
builder.AllowAnyMethod();
});
});
services.AddControllers();
services.AddDbContextPool<TwinEnginesDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("Standard")));
services.AddScoped<ICustomerTypeRepository, CustomerTypeRepository>();
services.AddScoped<ICustomerTypeService, CustomerTypeService>();
services.AddScoped<ICustomerRepository, CustomerRepository>();
services.AddScoped<ICustomerService, CustomerService>();
services.AddAutoMapper(Assembly.GetExecutingAssembly());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors(MyAllowSpecificOrigins);
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
Edited: I'm including the CustomersController.cs code plus the details from the HTTP request.
CustomersController.cs
[Route("api/[controller]")]
[ApiController]
public class CustomersController : ControllerBase
{
private readonly ICustomerService _customerService;
private readonly ICustomerTypeService _typeService;
private readonly IMapper _mapper;
public CustomersController(ICustomerService customerService, ICustomerTypeService typeService, IMapper mapper)
{
this._customerService = customerService ?? throw new ArgumentNullException(nameof(customerService));
this._typeService = typeService ?? throw new ArgumentNullException(nameof(typeService));
this._mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
}
// [EnableCors("MyAllowSpecificOrigins")]
[HttpGet("")]
public async Task<ActionResult<IEnumerable<CustomerDTO>>> GetCustomers()
{
var customers = await _customerService.GetAllAsync();
var response = _mapper.Map<IEnumerable<CustomerDTO>>(customers);
return Ok(response);
}
}
Request image:
Any ideas, thoughts? I really need your help folks, this is a technical assignment for a dev job.
Try to use the setting without the
slash at the end: builder.WithOrigins("http://localhost:3000");
After the change please do a clean and rebuild the project, as it might be a thing.
Also, you don't need a proxy setting on the JS side.
P.S. A mode for the request might not be set properly on the Axios side. In case the solution above doesn't work try to use:
axios(requestURL, { mode: 'cors' })
Try to add this attribute to your controllers
[EnableCors(MyAllowSpecificOrigins)]

Identity server 4: intercept 302 and replace it with 401

I've got an app which is hosting simultaneously Identity Server 4 and a client app (Vue) which uses a couple of rest services defined in an area for managing the site. The idea is that users associated with a specific role can access the client app and call the rest services for performing the actions.
Currently, my problem is that when the api return 302 when the user doesn't belong to the admin role. I'd like to change this to a 401, but I'm having some problems with it.
If this was a simple aspnet core app, then I'd simply pass a lambda to the OnRedirectToLogin property of the cookie handler that takes care of the request. Unfortunately, IS4 will only allow me to set a couple of basic settings of the cookie (expiration and sliding). The same docs say that I can override the cookie handler. So, I've tried doing the following:
services.AddIdentityServer()
... // other configurations
services.AddAuthentication(sharedOptions => {
sharedOptions.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;//IdentityServerConstants.ExternalCookieAuthenticationScheme;
sharedOptions.DefaultChallengeScheme = IdentityServerConstants.SignoutScheme;
})
... //other external providers...
.AddCookie( CookieAuthenticationDefaults.AuthenticationScheme, options => {
options.Events = new CookieAuthenticationEvents {
OnRedirectToLogin = ctx => {
if (ctx.Request.Path.StartsWithSegments("/Admin", StringComparison.OrdinalIgnoreCase)) {
ctx.Response.StatusCode = (int) HttpStatusCode.Unauthorized;
}
return Task.CompletedTask;
};
});
I expected to seem my handler being called whenever a request is redirected to the login page, but it never happens. Can anyone help?
Thanks
EDIT: just to add that I'm also using aspnet identity for managing the user accounts...
Posting the answer here in case anyone is interested...
After some digging, I've found that using identity means that you can't customize the cookie handler by doing what I was doing. Fortunately, the ConfigureAuthenticationEvent that can be configured by the ConfigureApplicationCookie extension method already does the right thing: if it detects that the current request is an AJAX call, it will return 401; if not, it will return 302. And here was the problem: the request made from the vue client wasn't being considered an AJAX request because it wasn't setting the X-Request-With header to XMLHttpRequest.
So, all it was required was to configure axios to set the header in all the calls:
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
I wrote a middleware sometime ago for this exact purpose and never looked back so if you don't find better solution, perhaps the solution can help you as well:
public class RedirectHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RedirectHandlingMiddleware> _logger;
public RedirectHandlingMiddleware(RequestDelegate next, ILogger<RedirectHandlingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
await HandleRedirect(context, ex);
await _next(context);
}
private Task HandleRedirect(HttpContext context)
{
if (context.Request.Path.StartsWithSegments("/Admin", StringComparison.OrdinalIgnoreCase) && context.Response.StatusCode == 302)
{
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
}
return Task.CompletedTask;
}
}
Just need to register in Startup.cs:
app.UseAuthentication();
app.UseMiddleware<RedirectHandlingMiddleware>();

Infinite authentication loop when using identityserver4 in asp.net core 2.0

I have an Identity Server using identityserver4 framework, its url is http://localhost:9000
My web application is asp.net core 2.0, its url is http://localhost:60002. This application will use the login page of Identity Server.
I want after logging in, the Identity Server will redirect to the application page (http://localhost:60002)
Here is the Startup.cs of client application
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
private string AuthorityUri => Configuration.GetValue<string>("UserManagement-Authority");
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = AuthorityUri; // "http://localhost:9000"
options.RequireHttpsMetadata = false;
options.ClientId = "customer.api";
options.ClientSecret = "testsecret";
options.ResponseType = "code id_token";
options.Scope.Add("customerprivatelinesvn.api");
options.Scope.Add("offline_access");
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
});
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)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
{
HotModuleReplacement = true
});
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
}
}
Here is the loggin page on Identity Server
But there is an infinite loop that calls to http://localhost:9000/connect/authorize endpoint, and then it returns to http://localhost:60002/signin-oidc with "Bad Request - Request Too Long" as below.
When I look at the cookies, there ar lots of items ".AspNetCore.Correlation.OpenIdConnect.xxx"
Here is the log on Identiy Server. It said that Identiy.Application was successfully authenticated.
Does anyone know what this problem is? And how to resolve this? Thank you very much.
Best regards,
Kevin
I also had a login loop after copying the startup code from an existing .NET Core 2.2 project and reused it in a new .NET Core 3.1 project.
The problem here was, that the app.UseAuthentication() must be called before the new app.UseAuthorization();
https://learn.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-3.1&tabs=visual-studio#migrate-startupconfigure
Only in case someone is running into this issue too...
Adding default Identity in the client app would cause an infinite redirect loop.
In the client app, if you need to use UserManager, RoleManager.
Then use the below code.
services.AddIdentityCore<IdentityUser>()
.AddRoles<IdentityRole>()
.AddRoleManager<RoleManager<IdentityRole>>()
.AddSignInManager<SignInManager<IdentityUser>>()
.AddEntityFrameworkStores<ApplicationDbContext>();
In your client app, in Startup check if you have something like
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
Remove that part and try again.
In my case, I was missing RedirectUri when initiating the Signin from the client. Problem solved by adding the RedirectUri as below.
public IActionResult SignIn()
{
return Challenge(new AuthenticationProperties() { RedirectUri = "/" }, "oidc" );
}
Well, you do have a very long request shown there in your Identity Server log - and the error says "Bad Request - request too long". I'd guess that the problem is that your request is too big :)
maximum length of HTTP GET request?
Have you tried posting rather than using a GET?
This issue was solved after I updated the latest nuget package of IdentityServer4 and .NET Core.

Getting a XMLHttpRequest: Network Error 0x80070005, Access is denied

I have a token endpoint that is passed a username and password grant type to authenticate users. This token endpoint is called from an AngularJS service that is part of my MVC web front end. When I call the service I get the following error
XMLHttpRequest: Network Error 0x80070005, Access is denied.
This seems to be a CORS problem. I did the following to resolve this problem with no luck thus far
I added the app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll); to my Startup.cs file for my token web service.
public class Startup
{
public void Configuration
(
IAppBuilder app
)
{
ConfigureOAuth(app);
var config = new HttpConfiguration();
WebApiConfig.Register(config);
config.Filters.Add(new AuthorizeAttribute());
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
private void ConfigureOAuth
(
IAppBuilder app
)
{
app.UseOAuthAuthorizationServer(new OAuthServerOptionsProvider().Provide());
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
I also have a class that is overriding the OAuthAuthorizationServerProvider, within the GrantResourceOwnerCredentials method I have the following code to add all origins to the response headers
public override async Task GrantResourceOwnerCredentials
(
OAuthGrantResourceOwnerCredentialsContext context
)
{
System.Web.HttpContext.Current.Response.Headers.Add("Access-Control-Allow-Origin", "*");
// Other code left out to keep this short
}
In fiddler I can see that the response header was successfully added
Is there something I'm missing here?
Update
Here is my request headers
First of all I have to point out that the comments made by #maurycy helped me find the solution in the comments of this stackoverflow post.
This post explains that the app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll); in the Startup.cs file should be moved to the top of the method and that the System.Web.HttpContext.Current.Response.Headers.Add("Access-Control-Allow-Origin", "*"); should be removed from the GrantResourceOwnerCredentials class. So I changed the code in my question to look something like this,
public class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureOAuth(app);
var config = new HttpConfiguration();
WebApiConfig.Register(config);
config.Filters.Add(new AuthorizeAttribute());
app.UseWebApi(config);
}
private void ConfigureOAuth(IAppBuilder app)
{
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseOAuthAuthorizationServer(new OAuthServerOptionsProvider().Provide());
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
I also have changed the class that is overriding the OAuthAuthorizationServerProvider
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// Remove the response header that was added
// Other code left out to keep this short
}

Web Api 2 with two method with the same HTTP verb an angularjs resources

I have this controller:
public class SeguiAttivazioneController : ApiController
{
[HttpGet]
public IHttpActionResult DoWork1()
{
...
return Ok();
}
[HttpGet]
public IHttpActionResult DoWork2()
{
...
return Ok();
}
[HttpGet] //I would like to have a search with GET verb, but I cannot validate my ModelState with dataAnnotation
public IHttpActionResult AnotherSearch(string filter1, string filter2, ...)
{
if (ModelState.IsValid)
{
...
return Ok();
}
return BadRequest(ModelState);
}
[HttpPost]
public IHttpActionResult DoSearch(SearchFilter filters)
{
if (ModelState.IsValid)
{
...
return Ok();
}
return BadRequest(ModelState);
}
[HttpPost]
public IHttpActionResult SubmitForm(FormData data)
{
...
return Ok();
}
}
As you can see I have two methods with same HttpVerbs (2 for GET and 2 for POST)... I don't know if I am violating REST principles... If so, I would like to avoid...
In this moment I am using AngularJs + NgResources to call my Controller..
public_area
.factory("SeguiAttivazioneService", function ($resource) {
//return {
// seguiAttivazione: $resource("/api/SeguiAttivazione/", null,
// {
// 'get2': { method: 'GET', url: '/api/SeguiAttivazione/GetActivationStatus2' }
// })
//};
return {
seguiAttivazione: $resource("/api/SeguiAttivazione/")
};
});
I am trying to do a GET:
$scope.getActivationStatus = function (event) {
event.preventDefault();
if ($scope.segui_attivazione_form.$valid) {
var request =
new SeguiAttivazioneService
.seguiAttivazione()
.$get({ }, getActivationStatusSuccess, getActivationStatusError);
}
};
But (correctly) I obtain an "Internal Server Error 500", because I have to GET method. How Can I solve this problem? (I suppose I will have same problem with POST too)
Thank you
UPDATE
Here the class of the filters
public class SearchFilter
{
[Required(ErrorMessage="")]
public string CodiceFiscale { get; set; }
[Required(ErrorMessage = "")]
[RegularExpression(#"^(?:\d{11,16})|(?:[a-zA-Z]{6}[a-zA-Z0-9]{2}[a-zA-Z][a-zA-Z0-9]{2}[a-zA-Z][a-zA-Z0-9]{3}[a-zA-Z])$", ErrorMessage = "Codice Fiscale o Partita IVA non validi")]
public string CodiceRichiesta { get; set; }
}
With this class I can use data Annotation to validate my model... If I do a GET Method I cannot use data annotation validation anymore...
Here is some explanation about a the REST Endpoints.
In REST we are manipulating ressources. As collections or individual.
Classics endpoint would be :
GET /rest/houses DATA : none -> will return a collection of houses
GET /rest/houses/{id} DATA : none -> will return the house find by its {id}
POST /rest/houses DATA : {"street":"3 bdv NY-city"} -> will create a new house object with the given data
PUT /rest/houses/{id} DATA : { "id":"{id}", "street":"4 bvd NY-city"} -> will update the whole house ressource find by its {id}
PATCH /rest/houses/{id} DATA : { "street":"4bvd NY-city" } -> will update the given fields of the house ressource find by its {id}
DELETE /rest/houses/{id} DATA : none -> will delete the house ressource find by its id.
There is too much things to know about restfull API that i can't give you all the keys. But try to find some good articles on the subjects such as :
http://www.restapitutorial.com/index.html
Not sure if this answer your question, but i hope it'll help you.
EDIT 1 :
Since i have to add some point about a restfull way to give some complicated action i'll give you the restfull url way to go.
In a restful world (extremely rare) you know only one entry point of your rest API let say this :
GET /rest/
This uri will respond you will all the services that the api can provide
Exemple :
{
"resources":"/rest/ressources",
"apiInfo" : "/rest/api/info"
}
To get your ressources informations you'll follow the link
GET response.resources
I may respond something like :
{
"houses":"/rest/ressources/houses/",
"cars" :"/rest/ressources/cars"
}
Now we want the houses
GET response.houses
Response :
{
"fields":[{
"constructionYear","street"
}],
"search":"/rest/houses"
"create":"/rest/houses"
}
etc... And at this place you can add some non restful endpoints. In a restful way. This action will be hold by a restful resource. Somes API that are using this kind of great Restful.
Standard Rest API :
https://developers.soundcloud.com/docs/api/reference#users
Restful API :
https://www.salesforce.com/us/developer/docs/api_rest/
The question is that the Web API infrastructure must have a way to choose one of the possible methods.
One way is changing the Web API route configuration, including an /{action}/ segment. If you do so it will work exactly like MVC, and you have to always include the action name.
The other way is making the received parameters different in each method, so that the Web API infrastructure can discover which method you're trying to invoke. You can read this answer I've written today for a similar question: How can I add multiple Get actions with different input params when working RESTFUL?.
As a final comment in that answer I say that the parameters can be also discerned by using route contraints.
The first solution of having to include the action name in all invocation is not RESTful, but do you need or prefer it to be RESTful for any particular reason?

Resources