Skip to content

fix(auth): keep the session when the token refresh cannot reach the identity provider - #1014

Merged
yeongseon merged 2 commits into
mainfrom
fix/issue-983-transient-refresh-failure
Oct 13, 2026
Merged

yeongseon merged 2 commits into
mainfrom
fix/issue-983-transient-refresh-failure

Conversation

@yeongseon

Copy link
Copy Markdown
Collaborator

무엇을

keycloak.ts 는 updateToken() 이 거부되면 이유와 상관없이 null 을 돌려줬고, init.ts 의 401 복구는 null 이면 세션을 비웠습니다. 그래서 토큰 엔드포인트에 닿지 못한 경우(네트워크 오류, 시간 초과, 5xx)도 refresh token 이 거부된 경우와 똑같이 로그아웃으로 이어졌습니다. 세션은 아직 유효한데도 신원, 제공자 키, 계정별로 저장된 상태가 함께 지워졌습니다.

Closes #983

어떻게

  • 갱신 결과를 세 가지로 나눴습니다 (RefreshOutcome): fresh(쓸 토큰이 있음), ended(세션이 끝남), unavailable(IdP 에 닿지 못함). fix(auth): renew for a forced refresh instead of reusing a regular one in flight #998 의 강제 갱신 합류 규칙은 그대로이고, 합류하는 promise 가 토큰 대신 이 결과를 담습니다. getFreshToken 은 시그니처를 유지합니다.
  • 확정적인 거부: 400(invalid_grant, keycloak-js 가 세션을 직접 끝냄), 401, 그리고 알 수 없는 오류는 지금처럼 재시도 없이 로그아웃합니다. 판단이 서지 않으면 막는 쪽입니다.
  • 일시적인 실패: TypeError(fetch 실패), 시도당 10초 시간 초과(keycloak-js 는 시간 제한이 없음), 5xx/408/429 는 0.5초, 1.5초 뒤 두 번 재시도합니다. 그래도 실패하면 신원과 저장 상태를 유지하고, 해당 요청은 다시 보내지 않고 실패시키며, 토큰은 보내지 않습니다(이전 토큰도 안 보냄). 화면 상단에 "로그인 서비스에 연결할 수 없습니다. 로그인 상태는 유지됩니다" 안내를 띄우고, 갱신이 성공하면 사라집니다.
  • 5초 쿨다운: 실제 Keycloak E2E 에서 확인한 문제입니다. 화면의 다른 요청이 토큰 없이 나갔다가 401 을 받으면 각자 재시도를 처음부터 시작해서, 재시도 횟수가 장애 한 번이 아니라 요청 하나마다 제한되고 있었습니다. 이제 IdP 에 닿지 못한 뒤 5초 동안은 강제 갱신이 바로 unavailable 을 돌려주고, 일반 갱신은 재시도 없이 한 번만 시도합니다.
  • 토큰은 로그에 남기지 않습니다. 이번 변경에 로그 출력은 없습니다.

확인

무엇 결과
__tests__/authTransientRefresh.test.ts (새 파일) 13개 통과
그중 수정 전 코드에서 실패하는 것 10개 (init.ts·keycloak.ts·store.ts 를 origin/main 으로 되돌려 실행. 통과하는 3개는 invalid_grant 로그아웃, 401 로그아웃, 이전 토큰을 보내지 않음 — 지켜야 하는 기존 동작)
src/features/auth 기존 테스트 통과 (keycloak.test.ts 의 #984 테스트 포함)
실제 Keycloak E2E (node scripts/run-keycloak-e2e.mjs, 로컬) 3개 통과: 기존 #903, 새 "닿지 않으면 유지, 다시 닿으면 갱신", 새 "invalid_grant 는 한 번 시도 뒤 로그아웃"
check-e2e-evidence.mjs 자격 증명 없음
lint(--max-warnings 0, 새 위반 없음) · typecheck · i18n 4종 · stale-ui · 전체 npm test(3764 통과) · build(번들 예산 안) 통과

단위 테스트는 네트워크 오류, 502, 503, 시간 초과, 재시도 중 복구, 재시도 상한, 쿨다운을 다루고, invalid_grant, 토큰 엔드포인트 401, 명시적 로그아웃은 별도로 확인합니다. E2E 는 실제 realm 의 refresh grant 만 가로채서 연결을 끊고(6초 동안 갱신 시도 3번 이하, 로그인 유지, 안내 표시), 다시 열면 갱신되고 안내가 사라지는지, 별도 테스트에서 400 invalid_grant 를 돌려주면 한 번만 시도하고 로그인 화면으로 가는지 봅니다.

범위

Scope exception: NO
Existing flow or risk: 기존 로그인·로그아웃 흐름은 그대로입니다. 바뀌는 것은 IdP 에 닿지 못했을 때뿐이고, 그때 세션을 비우지 않습니다. 위험은 IdP 장애 중 일시적인 실패로 분류된 상태가 길어지는 것인데, 그동안 토큰은 보내지 않으므로 보호된 요청은 실패한 채로 막혀 있습니다. 새 공개 API, 새 사용자 흐름, 새 서비스는 없습니다.

하지 않은 것

🤖 Generated with Claude Code

…dentity provider

A network error, a timeout or a 5xx from the token endpoint was treated like a
refused refresh token: the 401 recovery cleared the store and signed the user out.
A refresh now reports fresh / ended / unavailable. Refusals (400 invalid_grant,
401) sign out as before without retry; transient failures retry twice with
backoff, then keep the identity, send no token, and show a notice. A 5s cool-down
stops each refused request on a screen from starting its own retry series.

Closes #983

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@yeongseon yeongseon added the epic:trust Trust & Evidence — verification, drift, provenance label Oct 13, 2026
… sign out first

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@yeongseon
yeongseon merged commit 5c229e2 into main Oct 13, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

epic:trust Trust & Evidence — verification, drift, provenance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(auth): preserve session state on transient OIDC refresh transport failures

1 participant