Found by the E2E anonymous walk (epic #307) after moving the suite to the allowlisted https://localhost:4200 origin.
Symptom
GET /api/system/version from an allowlisted origin intermittently fails with No 'Access-Control-Allow-Origin' header is present, and the SPA's version badge falls back to OFFLINE. The origin is in the CORS allowlist — other endpoints work fine.
Root cause
Middleware order in SpikerSoft.Api/Extensions/WebApplicationExtensions.cs (ConfigureMiddleware): RedisRateLimitMiddleware and UseRateLimiter() are registered beforeUseCors(). When the limiter rejects a request (the version endpoint allows only 10/min/IP with QueueLimit = 0), the 429 short-circuits the pipeline before the CORS middleware runs, so the response has no Access-Control-Allow-Origin header and the browser reports a CORS failure instead of the real 429. Any rate-limited endpoint has the same behavior; the version endpoint is just the easiest to trip (the SPA calls it on every page load).
Fix
Move app.UseCors(...) above the two rate-limiting middlewares so preflights and 429s still carry CORS headers. Matches ASP.NET Core's recommended ordering (CORS before rate limiter).
Found by the E2E anonymous walk (epic #307) after moving the suite to the allowlisted `https://localhost:4200` origin.
## Symptom
`GET /api/system/version` from an allowlisted origin intermittently fails with `No 'Access-Control-Allow-Origin' header is present`, and the SPA's version badge falls back to OFFLINE. The origin *is* in the CORS allowlist — other endpoints work fine.
## Root cause
Middleware order in `SpikerSoft.Api/Extensions/WebApplicationExtensions.cs` (`ConfigureMiddleware`): `RedisRateLimitMiddleware` and `UseRateLimiter()` are registered **before** `UseCors()`. When the limiter rejects a request (the version endpoint allows only 10/min/IP with `QueueLimit = 0`), the 429 short-circuits the pipeline before the CORS middleware runs, so the response has no `Access-Control-Allow-Origin` header and the browser reports a CORS failure instead of the real 429. Any rate-limited endpoint has the same behavior; the version endpoint is just the easiest to trip (the SPA calls it on every page load).
## Fix
Move `app.UseCors(...)` above the two rate-limiting middlewares so preflights and 429s still carry CORS headers. Matches ASP.NET Core's recommended ordering (CORS before rate limiter).
Part of epic #307.
spikerj
added the bug label 2026-07-01 21:14:08 +00:00
Resolved in spikersoft-backend PR #50 (merged to master). UseCors now runs before response caching and both rate limiters, so 429s carry Access-Control-Allow-Origin instead of masquerading as CORS failures. Closing.
Resolved in spikersoft-backend PR #50 (merged to `master`). `UseCors` now runs before response caching and both rate limiters, so 429s carry `Access-Control-Allow-Origin` instead of masquerading as CORS failures. Closing.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Found by the E2E anonymous walk (epic #307) after moving the suite to the allowlisted
https://localhost:4200origin.Symptom
GET /api/system/versionfrom an allowlisted origin intermittently fails withNo 'Access-Control-Allow-Origin' header is present, and the SPA's version badge falls back to OFFLINE. The origin is in the CORS allowlist — other endpoints work fine.Root cause
Middleware order in
SpikerSoft.Api/Extensions/WebApplicationExtensions.cs(ConfigureMiddleware):RedisRateLimitMiddlewareandUseRateLimiter()are registered beforeUseCors(). When the limiter rejects a request (the version endpoint allows only 10/min/IP withQueueLimit = 0), the 429 short-circuits the pipeline before the CORS middleware runs, so the response has noAccess-Control-Allow-Originheader and the browser reports a CORS failure instead of the real 429. Any rate-limited endpoint has the same behavior; the version endpoint is just the easiest to trip (the SPA calls it on every page load).Fix
Move
app.UseCors(...)above the two rate-limiting middlewares so preflights and 429s still carry CORS headers. Matches ASP.NET Core's recommended ordering (CORS before rate limiter).Part of epic #307.
Resolved in spikersoft-backend PR #50 (merged to
master).UseCorsnow runs before response caching and both rate limiters, so 429s carryAccess-Control-Allow-Origininstead of masquerading as CORS failures. Closing.