I created an ASP.NET Core 9 Minimal API that is consumed by an Angular app. Both applications are on a IIS site and the Angular app is calling the backend with http://localhost/api/.... Both are on port 80.
Initially I got a CORS error and then I added the configuration shown here to my Program.cs in my backend. When I open the site in the server and test it, I get no CORS error, but when I try to do the same from my machine, I get these errors:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost/api/auth/login. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 200.
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost/api/auth/login. (Reason: CORS request did not succeed). Status code: (null).
I tried a bunch of different configurations to try fixing this problem like adding .AllowAnonymous() to the endpoint mapping, tried forcing the CORS headers with:
app.Use((context, next) =>
{
context.Response.Headers["Access-Control-Allow-Origin"] = "*";
context.Response.Headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS, PUT, DELETE";
context.Response.Headers["Access-Control-Allow-Headers"] = "*";
return next();
});
I also called the endpoint with a curl request to check if the header was present:
curl -i -X OPTIONS http://<mydomain>/api/auth/login -H "Origin: http://<mydomain>" -H "Access-Control-Request-Method: POST"
HTTP/1.1 204 No Content
Server: Microsoft-IIS/10.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST
X-Powered-By: ASP.NET
Date: Fri, 04 Apr 2025 20:53:12 GMT
But nothing seems to work. I know for a fact that no exceptions are occurring when the endpoint is called, so this is not a 500 error disguised as a CORS error (it works when called from within the server).
Here is my Program.cs and the custom service extensions that I created to make the API work:
Program.cs:
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
var secret = builder.Configuration["AppSettings:Jwt:Secret"];
var issuer = builder.Configuration["AppSettings:Jwt:Issuer"];
var audience = builder.Configuration["AppSettings:Jwt:Audience"];
var environment = builder.Configuration["AppSettings:Environment"];
ArgumentException.ThrowIfNullOrEmpty(secret);
ArgumentException.ThrowIfNullOrEmpty(issuer);
ArgumentException.ThrowIfNullOrEmpty(audience);
builder.Services
.AddOpenApi()
.AddMemoryCache()
.AddHttpContextAccessor()
.AddEndpointsApiExplorer()
.AddRouting()
.AddAuthorization()
.AddExceptionHandler<ExceptionHandler>()
.AddProblemDetails()
.AddEndpoints()
.AddApplication()
.AddInfrastructure(builder.Configuration)
.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
policy.AllowAnyOrigin();
policy.AllowAnyMethod();
policy.AllowAnyHeader();
});
})
.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "REST Api",
Version = $"25.04.010 {environment}",
Description = "Rest API",
Contact = new OpenApiContact
{
Name = "Some Name",
Email = "[email protected]"
}
});
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Description = "JWT Authorization Bearer Token.",
In = ParameterLocation.Header,
Name = "Authorization",
Type = SecuritySchemeType.Http,
Scheme = JwtBearerDefaults.AuthenticationScheme
});
options.OperationFilter<SecurityRequirementsOperationFilter>();
options.CustomSchemaIds(type => type.ToString());
})
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = issuer,
ValidAudience = audience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret))
};
});
builder.Host.UseSerilog((context, configuration) =>
{
configuration.ReadFrom.Configuration(context.Configuration);
});
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.UseExceptionHandler();
app.UseRouting();
app.UseCors();
// app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.UseSerilogRequestLogging();
app.MapOpenApi();
app.MapEndpoints();
app.Run();
}
}
Endpoint extension:
public static class EndpointExtension
{
public static IServiceCollection AddEndpoints(this IServiceCollection services)
{
services.TryAddEnumerable(Assembly.GetExecutingAssembly()
.DefinedTypes
.Where(type => type is { IsAbstract: false, IsInterface: false } && type.IsAssignableTo(typeof(IEndpoint)))
.Select(type => ServiceDescriptor.Transient(typeof(IEndpoint), type))
.ToArray());
return services;
}
public static IApplicationBuilder MapEndpoints(this WebApplication app, RouteGroupBuilder? groupBuilder = null)
{
var endpoints = app.Services.GetRequiredService<IEnumerable<IEndpoint>>();
IEndpointRouteBuilder builder = groupBuilder is null ? app : groupBuilder;
foreach (var endpoint in endpoints)
{
endpoint.MapEndpoint(builder);
}
return app;
}
}
Login endpoint:
public class LoginEndpoint : IEndpoint
{
public void MapEndpoint(IEndpointRouteBuilder app)
{
app.MapPost("/auth/login", async (Login login, IAuthService authService) =>
{
var result = await authService.Login(login);
return result.IsSuccess ? Results.Ok(result.Value) : result.Problem();
})
.WithTags("Auth")
.WithValidation<Login>()
.Produces<Token>(StatusCodes.Status200OK)
.Produces<ProblemDetails>(StatusCodes.Status400BadRequest)
.Produces<ProblemDetails>(StatusCodes.Status500InternalServerError);
}
}
I'm starting to lose my mind with this, since nothing seems to work. Any help would be greatly appreciated. Thanks.
UseCorsshould be afterUseRoutingand beforeUseAuthenticationandUseAuthorizationexactly as it is in myProgram.cs. ShouldAddCorsbe beforeAddAuthorizationas well?The UseCors middleware must be called between UseRouting and UseAuthorization, just like in my code, so i still dont see the problem.