Skip to content

Commit 3de4fe2

Browse files
fix: return 401/403 for API endpoints instead of redirecting
Cookie auth's HandleChallengeAsync issues a 302 redirect to the login page by default. For fetch() API calls this causes the request to follow the redirect, eventually hitting MapFallbackToController and returning a user-visible 404. Add OnRedirectToLogin and OnRedirectToAccessDenied handlers in ConfigureApplicationCookie that return 401/403 for /api/* paths, leaving the redirect behavior intact for browser page navigation. This fixes POST /api/chat/stream returning a 404 for unauthenticated users instead of a proper 401.
1 parent 409f37e commit 3de4fe2

2 files changed

Lines changed: 30 additions & 0 deletions

File tree

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
<PackageVersion Include="ContentFeedNuget" Version="$(ToolingPackagesVersion)" />
2020
</ItemGroup>
2121
<ItemGroup>
22+
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" Version="10.0.6" />
2223
<PackageVersion Include="AspNet.Security.OAuth.GitHub" Version="10.0.0" />
2324
<PackageVersion Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.5.0" />
2425
<PackageVersion Include="Azure.Identity" Version="1.21.0" />

EssentialCSharp.Web/Program.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Threading.RateLimiting;
2+
23
using EssentialCSharp.Chat.Common.Extensions;
34
using EssentialCSharp.Web.Areas.Identity.Data;
45
using EssentialCSharp.Web.Areas.Identity.Services.PasswordValidators;
@@ -111,6 +112,15 @@ private static void Main(string[] args)
111112
var initialLogger = loggerFactory.CreateLogger<Program>();
112113

113114
builder.Services.AddDbContext<EssentialCSharpWebContext>(options => options.UseSqlServer(connectionString, sql => sql.EnableRetryOnFailure(5)));
115+
116+
// Data Protection — persist keys in SQL Server so they survive container restarts.
117+
// Local dev: SQL Server container with WithDataVolume keeps data between restarts.
118+
// Production (ACA via Terraform): Azure SQL connection string injected via env vars.
119+
// SetApplicationName ensures the discriminator is stable across container hostname changes.
120+
builder.Services.AddDataProtection()
121+
.SetApplicationName("EssentialCSharpWeb")
122+
.PersistKeysToDbContext<EssentialCSharpWebContext>();
123+
114124
builder.Services.AddDefaultIdentity<EssentialCSharpWebUser>(options =>
115125
{
116126
// Password settings
@@ -145,6 +155,25 @@ private static void Main(string[] args)
145155
options.Cookie.HttpOnly = true;
146156
options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
147157
options.SlidingExpiration = true;
158+
// API endpoints must return 401/403 instead of redirecting to the login page.
159+
// Cookie auth's default behavior (302 redirect) causes fetch() to follow the
160+
// redirect, eventually hitting the fallback controller and returning a 404.
161+
options.Events.OnRedirectToLogin = context =>
162+
{
163+
if (context.Request.Path.StartsWithSegments("/api"))
164+
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
165+
else
166+
context.Response.Redirect(context.RedirectUri);
167+
return Task.CompletedTask;
168+
};
169+
options.Events.OnRedirectToAccessDenied = context =>
170+
{
171+
if (context.Request.Path.StartsWithSegments("/api"))
172+
context.Response.StatusCode = StatusCodes.Status403Forbidden;
173+
else
174+
context.Response.Redirect(context.RedirectUri);
175+
return Task.CompletedTask;
176+
};
148177
});
149178

150179
if (builder.Environment.IsDevelopment())

0 commit comments

Comments
 (0)