Skip to content

Commit fee90b1

Browse files
authored
feat(auth): improve token refresh on optional routes for better ux (#211)
- Always attempt token refresh on optional auth routes (e.g., /meetings/) - Implement graceful failure handling based on route type - Optional routes: log warning and continue without token (no logout) - Required routes: log error and force logout for re-authentication - Replace attemptRefresh parameter with isOptionalRoute for clearer intent - Simplify auth decision logic by removing redundant checks Benefits: - Authenticated users get enhanced features when possible - No interruption to meeting access if refresh fails - Better logging differentiation (WARNING vs ERROR) - No redirect loops on optional routes - Maintains backward compatibility LFXV2-913 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Asitha de Silva <[email protected]>
1 parent a3c51a0 commit fee90b1

File tree

1 file changed

+33
-36
lines changed

1 file changed

+33
-36
lines changed

apps/lfx-one/src/server/middleware/auth.middleware.ts

Lines changed: 33 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)