diff --git a/backend/app/main.py b/backend/app/main.py index 75af775..e1d5733 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -116,7 +116,7 @@ async def auth_callback(payload: AuthCallbackRequest): raise HTTPException(status_code=400, detail=f"OIDC token exchange failed: {response.text}") result = response.json() - logger.debug("OIDC token exchange result keys: %s", list(result.keys())) + logger.debug("OIDC callback result keys: %s", list(result.keys())) if "access_token" not in result: logger.warning("OIDC token response missing access_token; may return only id_token or an opaque token") return result diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index edf7b3d..b804c66 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -125,8 +125,35 @@ function generateUUID() { return `${hex.substr(0, 8)}-${hex.substr(8, 4)}-${hex.substr(12, 4)}-${hex.substr(16, 4)}-${hex.substr(20, 12)}` } +function parseJwtPayload(token: string) { + try { + const [, payload] = token.split('.') + if (!payload) return null + const base64 = payload.replace(/-/g, '+').replace(/_/g, '/') + const decoded = atob(base64) + return JSON.parse(decodeURIComponent(decoded.split('').map((c) => `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}`).join(''))) + } catch { + return null + } +} + +function isJwtExpired(token: string) { + const payload = parseJwtPayload(token) + if (!payload || typeof payload.exp !== 'number') return false + return payload.exp < Math.floor(Date.now() / 1000) +} + function getLocalToken() { - return window.localStorage.getItem('oidc_access_token') + const token = window.localStorage.getItem('oidc_access_token') + if (!token) { + return null + } + if (isJwtExpired(token)) { + console.debug('Clearing expired stored token') + clearLocalToken() + return null + } + return token } function setLocalToken(token: string) {