3535using Intersect . Server . Web . Controllers ;
3636using Intersect . Server . Web . Controllers . Api ;
3737using Intersect . Server . Web . Controllers . AssetManagement ;
38+ using Intersect . Server . Web . Extensions ;
3839using Intersect . Server . Web . Types . Chat ;
3940using Microsoft . AspNetCore . Http . Features ;
41+ using Microsoft . Extensions . Primitives ;
4042using MyCSharp . HttpUserAgentParser . AspNetCore . DependencyInjection ;
4143using MyCSharp . HttpUserAgentParser . MemoryCache . DependencyInjection ;
4244
@@ -50,12 +52,6 @@ internal partial class ApiService : ApplicationService<ServerContext, IApiServic
5052 private WebApplication ? _app ;
5153 private static readonly Assembly Assembly = typeof ( ApiService ) . Assembly ;
5254
53- private static readonly string [ ] ChallengePaths = [
54- "/api" ,
55- "/assets" ,
56- "/avatar" ,
57- ] ;
58-
5955 private static string GetOptionsName < TOptions > ( ) => typeof ( TOptions ) . Name . Replace ( "Options" , string . Empty ) ;
6056
6157 // ReSharper disable once MemberCanBeMadeStatic.Local
@@ -76,11 +72,13 @@ internal partial class ApiService : ApplicationService<ServerContext, IApiServic
7672 return default ;
7773 }
7874
79- ApplicationContext . Context . Value ? . Logger . LogInformation (
75+ ApplicationContext . CurrentContext . Logger . LogInformation (
8076 "Launching Intersect REST API in '{EnvironmentName}' mode..." ,
8177 builder . Environment . EnvironmentName
8278 ) ;
8379
80+ builder . Services . AddSingleton ( ApplicationContext . GetCurrentContext < IApplicationContext > ( ) ) ;
81+
8482 var updateServerSection = builder . Configuration . GetSection ( GetOptionsName < UpdateServerOptions > ( ) ) ;
8583 builder . Services . Configure < UpdateServerOptions > ( updateServerSection ) ;
8684
@@ -173,30 +171,16 @@ internal partial class ApiService : ApplicationService<ServerContext, IApiServic
173171
174172 builder . Services . AddSingleton < IntersectAuthenticationManager > ( ) ;
175173
176- builder . Services . AddAuthentication ( BearerCookieFallbackAuthenticationScheme )
174+ builder . Services . AddAuthentication (
175+ options =>
176+ {
177+ options . DefaultScheme = BearerCookieFallbackAuthenticationScheme ;
178+ }
179+ )
177180 . AddCookie (
178181 CookieAuthenticationDefaults . AuthenticationScheme ,
179182 options =>
180183 {
181- // Commenting this out fixes no redirect
182- // Uncommenting fixed API consumers with expired tokens
183- // options.ForwardChallenge = JwtBearerDefaults.AuthenticationScheme;
184-
185- // So the thing that was broken if the above was commented out was the editor (or presumably
186- // anything that had an expired token) -- I believe the below fixes it
187- // options.ForwardDefaultSelector = context =>
188- // {
189- // var requestPath = context.Request.Path;
190- // foreach (var challengePath in ChallengePaths)
191- // {
192- // if (requestPath.StartsWithSegments(challengePath))
193- // {
194- // return JwtBearerDefaults.AuthenticationScheme;
195- // }
196- // }
197- //
198- // return null;
199- // };
200184 options . Events . OnSignedIn += async ( context ) => { } ;
201185 options . Events . OnSigningIn += async ( context ) => { } ;
202186 options . Events . OnSigningOut += async ( context ) => { } ;
@@ -221,7 +205,7 @@ internal partial class ApiService : ApplicationService<ServerContext, IApiServic
221205 return ;
222206 }
223207
224- var logger = context . HttpContext . RequestServices . GetRequiredService < ILogger < ApiService > > ( ) ;
208+ var logger = context . GetAPILogger ( ) ;
225209 logger . LogInformation (
226210 "Renewing cookie for {UserId}" ,
227211 updatedPrincipal . FindFirstValue ( ClaimTypes . NameIdentifier )
@@ -231,6 +215,34 @@ internal partial class ApiService : ApplicationService<ServerContext, IApiServic
231215 } ;
232216 options . Events . OnRedirectToLogin += async ( context ) =>
233217 {
218+ if ( context . HttpContext . IsPage ( ) )
219+ {
220+ context . GetAPILogger ( ) . LogTrace (
221+ "{OnRedirectToLogin} called for page (non-API) endpoint: {Route}" ,
222+ nameof ( CookieAuthenticationEvents . OnRedirectToLogin ) ,
223+ context . HttpContext . Request . Path
224+ ) ;
225+ return ;
226+ }
227+
228+ if ( context . HttpContext . IsController ( ) )
229+ {
230+ context . GetAPILogger ( ) . LogTrace (
231+ "{OnRedirectToLogin} called for controller (API) endpoint: {Route}" ,
232+ nameof ( CookieAuthenticationEvents . OnRedirectToLogin ) ,
233+ context . HttpContext . Request . Path
234+ ) ;
235+
236+ context . RedirectUri = string . Empty ;
237+ context . Response . StatusCode = ( int ) HttpStatusCode . Unauthorized ;
238+ context . Response . Headers . Location = StringValues . Empty ;
239+ }
240+
241+ context . GetAPILogger ( ) . LogWarning (
242+ "{OnRedirectToLogin} called for unclassified endpoint: {Route}" ,
243+ nameof ( CookieAuthenticationEvents . OnRedirectToLogin ) ,
244+ context . HttpContext . Request . Path
245+ ) ;
234246 } ;
235247 options . Events . OnRedirectToAccessDenied += async ( context ) =>
236248 {
@@ -256,15 +268,31 @@ internal partial class ApiService : ApplicationService<ServerContext, IApiServic
256268 } ;
257269 options . ForwardDefaultSelector += context =>
258270 {
259- var requestPath = context . Request . Path ;
260- foreach ( var challengePath in ChallengePaths )
271+ if ( context . IsController ( ) )
261272 {
262- if ( requestPath . StartsWithSegments ( challengePath ) )
263- {
264- return null ;
265- }
273+ return null ;
274+ }
275+
276+ if ( context . IsPage ( ) )
277+ {
278+ return CookieAuthenticationDefaults . AuthenticationScheme ;
266279 }
267280
281+ if ( context . Request . Headers . Authorization . Count > 0 )
282+ {
283+ context . GetAPILogger ( ) . LogWarning (
284+ "JwtBearer ForwardDefaultSelector invoked for unclassified endpoint, not forwarding because there is an Authorization header: {Route}" ,
285+ context . Request . Path
286+ ) ;
287+
288+ return null ;
289+ }
290+
291+ context . GetAPILogger ( ) . LogWarning (
292+ "JwtBearer ForwardDefaultSelector invoked for unclassified endpoint, falling back to cookie authentication because there is no Authorization header: {Route}" ,
293+ context . Request . Path
294+ ) ;
295+
268296 return CookieAuthenticationDefaults . AuthenticationScheme ;
269297 } ;
270298 builder . Configuration . Bind ( $ "Api.{ nameof ( JwtBearerOptions ) } ", options ) ;
@@ -277,13 +305,12 @@ internal partial class ApiService : ApplicationService<ServerContext, IApiServic
277305 } ,
278306 OnChallenge = async context =>
279307 {
280- if ( context . AuthenticateFailure != null )
308+ if ( context . AuthenticateFailure is not null || context . HttpContext . IsController ( ) )
281309 {
282310 // This was needed to make sure authentication failures didn't return 200
283311 context . Response . StatusCode = ( int ) HttpStatusCode . Unauthorized ;
284312 context . HandleResponse ( ) ;
285313 }
286- context . HandleResponse ( ) ;
287314 } ,
288315 OnMessageReceived = async context => { } ,
289316 OnTokenValidated = async context =>
@@ -331,7 +358,7 @@ internal partial class ApiService : ApplicationService<ServerContext, IApiServic
331358
332359 context . Fail ( "expired_token" ) ;
333360
334- // var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<ApiService>> ();
361+ // var logger = context.GetAPILogger ();
335362 // logger.LogInformation(
336363 // "Changing token for {UserId}",
337364 // updatedPrincipal.FindFirstValue(ClaimTypes.NameIdentifier)
@@ -343,7 +370,8 @@ internal partial class ApiService : ApplicationService<ServerContext, IApiServic
343370 SymmetricSecurityKey issuerKey = new ( tokenGenerationOptions . SecretData ) ;
344371 options . TokenValidationParameters . IssuerSigningKey = issuerKey ;
345372 }
346- ) . AddPolicyScheme (
373+ )
374+ . AddPolicyScheme (
347375 BearerCookieFallbackAuthenticationScheme ,
348376 "Bearer-to-Cookie Fallback" ,
349377 pso =>
@@ -354,10 +382,23 @@ internal partial class ApiService : ApplicationService<ServerContext, IApiServic
354382 {
355383 return JwtBearerDefaults . AuthenticationScheme ;
356384 }
357- else
385+
386+ if ( context . IsController ( ) )
387+ {
388+ return JwtBearerDefaults . AuthenticationScheme ;
389+ }
390+
391+ if ( context . IsPage ( ) )
358392 {
359393 return CookieAuthenticationDefaults . AuthenticationScheme ;
360394 }
395+
396+ context . GetAPILogger ( ) . LogWarning (
397+ "Trying to authenticate with no authorization header on unclassified endpoint, falling back to cookie authentication: {Route}" ,
398+ context . Request . Path
399+ ) ;
400+
401+ return CookieAuthenticationDefaults . AuthenticationScheme ;
361402 } ;
362403 }
363404 ) ;
@@ -583,7 +624,6 @@ internal partial class ApiService : ApplicationService<ServerContext, IApiServic
583624 app . UseResponseCaching ( ) ;
584625 app . UseOutputCache ( ) ;
585626
586-
587627 StaticFileOptions staticFileOptions = new ( )
588628 {
589629 HttpsCompression = HttpsCompressionMode . Compress ,
0 commit comments