@@ -82,48 +82,54 @@ function checkAuthentication(req: Request): boolean {
8282/**
8383 * Extracts bearer token from OIDC session if available
8484 * @param req - Express request object
85- * @param attemptRefresh - Whether to attempt token refresh if expired (default: true )
85+ * @param isOptionalRoute - Whether this is an optional auth route (affects logout behavior on refresh failure )
8686 */
87- async function extractBearerToken ( req : Request , attemptRefresh : boolean = true ) : Promise < TokenExtractionResult > {
87+ async function extractBearerToken ( req : Request , isOptionalRoute : boolean = false ) : Promise < TokenExtractionResult > {
8888 const startTime = logger . startOperation ( req , 'token_extraction' , {
8989 path : req . path ,
9090 hasOidc : ! ! req . oidc ,
9191 isAuthenticated : req . oidc ?. isAuthenticated ( ) ,
9292 hasAccessToken : ! ! req . oidc ?. accessToken ,
9393 isTokenExpired : req . oidc ?. accessToken ?. isExpired ( ) ,
9494 tokenValue : req . oidc ?. accessToken ?. access_token ? 'present' : 'missing' ,
95- attemptRefresh ,
95+ isOptionalRoute ,
9696 } ) ;
9797
9898 try {
9999 if ( req . oidc ?. isAuthenticated ( ) ) {
100100 // Check if token exists and is expired
101101 if ( req . oidc . accessToken ?. isExpired ( ) ) {
102- // For optional routes, don't attempt refresh - just skip token extraction
103- if ( ! attemptRefresh ) {
104- logger . success ( req , 'token_extraction' , startTime , {
105- path : req . path ,
106- token_extracted : false ,
107- reason : 'skipped_refresh_optional_route' ,
108- } ) ;
109- return { success : false , needsLogout : false } ;
110- }
111-
112102 try {
113- // Attempt to refresh the token
103+ // Always attempt to refresh the token for better UX
104+ // Authenticated users should get enhanced features when possible
114105 const refreshedToken = await req . oidc . accessToken . refresh ( ) ;
115106 if ( refreshedToken ?. access_token ) {
116107 req . bearerToken = refreshedToken . access_token ;
117108 logger . success ( req , 'token_refresh' , startTime , { path : req . path } ) ;
118109 return { success : true , needsLogout : false } ;
119110 }
120111 } catch ( refreshError ) {
121- logger . error ( req , 'token_extraction' , startTime , refreshError , {
122- path : req . path ,
123- failure_reason : 'token_refresh_failed' ,
124- } ) ;
125- // Token refresh failed, user needs to re-authenticate
126- return { success : false , needsLogout : true } ;
112+ // Different handling based on route type:
113+ // - Optional routes: Log warning and continue without token (no logout)
114+ // - Required routes: Log error and force logout to re-authenticate
115+ if ( isOptionalRoute ) {
116+ logger . warning ( req , 'token_extraction' , 'Token refresh failed on optional route - continuing without token' , {
117+ path : req . path ,
118+ failure_reason : 'token_refresh_failed' ,
119+ is_optional_route : isOptionalRoute ,
120+ error : refreshError instanceof Error ? refreshError . message : 'Unknown error' ,
121+ } ) ;
122+ } else {
123+ logger . error ( req , 'token_extraction' , startTime , refreshError , {
124+ path : req . path ,
125+ failure_reason : 'token_refresh_failed' ,
126+ is_optional_route : isOptionalRoute ,
127+ } ) ;
128+ }
129+
130+ // For optional routes, don't force logout - just continue without token
131+ // For required routes, user needs to re-authenticate
132+ return { success : false , needsLogout : ! isOptionalRoute } ;
127133 }
128134 } else if ( req . oidc . accessToken ?. access_token ) {
129135 // Token exists and is not expired
@@ -168,19 +174,9 @@ function makeAuthDecision(result: AuthMiddlewareResult, req: Request): AuthDecis
168174 }
169175
170176 // Optional auth routes - always allow but may have enhanced features
171- // Check this BEFORE needsLogout to ensure optional routes aren 't blocked
172- // when token refresh fails (the token is optional, so failure is acceptable )
177+ // Token refresh is attempted for better UX, but failures don 't block access
178+ // (needsLogout is always false for optional routes )
173179 if ( route . auth === 'optional' ) {
174- // For optional routes where token is not required, don't fail on token refresh issues
175- if ( ! route . tokenRequired && needsLogout ) {
176- logger . debug ( req , 'auth_decision' , 'Optional auth route with tokenRequired=false - ignoring token refresh failure' , {
177- path : req . path ,
178- routeType : route . type ,
179- authLevel : route . auth ,
180- tokenRequired : route . tokenRequired ,
181- } ) ;
182- }
183-
184180 logger . debug ( req , 'auth_decision' , 'Optional auth route - allowing access' , {
185181 path : req . path ,
186182 routeType : route . type ,
@@ -367,10 +363,11 @@ export function createAuthMiddleware(config: AuthConfig = DEFAULT_CONFIG) {
367363 let hasToken = false ;
368364 let needsLogout = false ;
369365 if ( routeConfig . tokenRequired || routeConfig . auth === 'optional' ) {
370- // For optional routes, don't attempt token refresh to avoid redirect loops
371- // when refresh token is invalid - just use existing valid token or none
372- const attemptRefresh = routeConfig . auth !== 'optional' ;
373- const tokenResult = await extractBearerToken ( req , attemptRefresh ) ;
366+ // Always attempt token refresh for better UX
367+ // Optional routes will handle refresh failures gracefully (no forced logout)
368+ // Required routes will force logout on refresh failure
369+ const isOptionalRoute = routeConfig . auth === 'optional' ;
370+ const tokenResult = await extractBearerToken ( req , isOptionalRoute ) ;
374371 hasToken = tokenResult . success ;
375372 needsLogout = tokenResult . needsLogout ;
376373 }
0 commit comments