9.9 KiB
PKCE RP Back-Channel Logout 구현 가이드
이 문서는 Baron SSO와 연동하는 PKCE RP가 Back-Channel Logout을 지원하려고 할 때 필요한 구현 기준을 정리합니다.
목적
PKCE RP도 OIDC Authorization Code + PKCE 흐름을 사용하면서 Baron SSO의 원격 세션 종료 이벤트를 받을 수 있어야 합니다. 다만 Back-Channel Logout은 브라우저가 아니라 OP(Baron)가 RP 서버로 직접 logout_token을 보내는 방식이므로, 순수 frontend-only PKCE 앱만으로는 구현할 수 없습니다.
즉, PKCE RP가 Back-Channel Logout을 사용하려면 다음 둘을 모두 가져야 합니다.
- PKCE 로그인 플로우를 시작하고 callback을 처리하는 RP
logout_token을 수신하는 서버 endpoint
적용 대상
이 가이드는 다음 경우를 대상으로 합니다.
- 브라우저에서
Authorization Code + PKCE를 사용하는 RP - RP가 자체 세션 또는 BFF 세션을 보유하는 경우
- RP가
Back-Channel Logout URI를 등록하고 Baron의 세션 종료 이벤트를 직접 수신하려는 경우
다음 경우는 이 가이드의 직접 대상이 아닙니다.
- 순수 frontend-only SPA
- 서버 없이
localStorage/sessionStorage만 사용하는 PKCE 앱
이 경우에는 Back-Channel Logout 대신 front-channel logout, 세션 재검증, 짧은 token TTL 같은 별도 전략을 사용해야 합니다.
devfront 등록 기준
PKCE RP는 devfront에서 아래 항목을 등록합니다.
Type:pkceRedirect URI: RP callback URLBack-Channel Logout URI: RP 서버 endpoint- 필요 시
SID Claim Required
예시:
Type: pkce
Redirect URI: https://rp.example.com/callback
Back-Channel Logout URI: https://rp.example.com/backchannel-logout
SID Claim Required: off
로컬 Docker 개발 예시:
Redirect URI: http://localhost:3333/callback
Back-Channel Logout URI: http://baron-sso-login-demo:3333/backchannel-logout
주의:
Back-Channel Logout URI는 브라우저 기준 주소가 아니라 Baron backend가 실제로 접근 가능한 주소여야 합니다.- Docker 환경에서는
localhost가 backend 컨테이너 자신을 가리킬 수 있으므로, Docker 서비스명이나 사설 IP를 사용해야 할 수 있습니다.
구현 요구사항
PKCE RP는 최소한 아래를 구현해야 합니다.
1. 로그인 후 세션 매핑 저장
RP는 callback 이후 아래 정보 중 하나 이상을 로컬 세션과 연결해야 합니다.
sid -> rpSessionIdsub -> rpSessionId
권장 순서는 다음과 같습니다.
sid를 우선 저장sub도 함께 저장- 한 사용자가 여러 브라우저 세션을 가질 수 있으므로
1:N구조를 가정
예시:
sid: 796f5cf7-37e7-494b-9b4c-26cc0c217a6a
sub: 8150cb83-a905-4b50-bdcf-d22046ecdc30
rpSessionId: DqKlQ8MbsGnn_jfOus1k03MFRDpuXCrj
2. POST /backchannel-logout endpoint
RP는 Baron이 서버 간으로 호출할 endpoint를 제공해야 합니다.
예:
POST /backchannel-logout
Content-Type: application/x-www-form-urlencoded
Body: logout_token=<jwt>
RP는 이 endpoint에서:
logout_token존재 여부 확인- JWT 서명 및 claim 검증
sid또는sub로 로컬 세션 탐색- 세션 스토어에서 직접 세션 파기
- 성공 시
2xx응답
을 수행해야 합니다.
3. logout_token 검증
RP는 Baron이 노출하는 Back-Channel Logout JWKS로 logout_token을 검증해야 합니다.
현재 Baron의 JWKS endpoint 예시는 다음과 같습니다.
GET /api/v1/auth/backchannel/jwks.json
검증 필수 항목:
- JWT 서명 검증
iss가 Baron OIDC issuer와 일치aud에 현재 RPclient_id포함iat존재jti존재events에http://schemas.openid.net/event/backchannel-logout포함nonce가 없어야 함sid또는sub가 있어야 함
추가 권장 항목:
jtireplay 방지 캐시- 시계 오차 허용 범위 설정
- 검증 실패 시
400
세션 종료 기준
권장 순서
sid로 매칭 시도- 매칭 실패 시
sub로 fallback
이 기준은 SID Claim Required 정책에 따라 달라집니다.
SID Claim Required = true
logout_token에sid가 있어야만 처리subfallback 금지- 세션 모델이
sid중심으로 안정적으로 유지되는 RP에 적합
SID Claim Required = false
sid가 있으면 우선 사용sid매칭이 안 되거나sid가 없어도sub로 fallback 가능- 실제 운영에서는 이 모드가 더 현실적일 수 있음
세션 파기 방식
Back-Channel Logout에서는 현재 브라우저 요청의 req.session.destroy()로는 부족합니다.
반드시 세션 스토어에서 session id를 찾아 직접 파기해야 합니다.
예:
store.destroy(rpSessionId)
필수 조건:
- 로그아웃 대상 세션 ID를 매핑 테이블에서 찾을 수 있어야 함
- 이미 삭제된 세션은 idempotent success 처리
권장 로그 항목
RP는 아래 정도의 로그를 남기는 것을 권장합니다.
- 요청 수신
- 토큰 검증 성공/실패
sid,sub,jti- 매칭된
rpSessionId목록 - 세션 파기 성공/실패 수
예시:
[백채널 로그아웃] 요청 수신
[백채널 로그아웃] 토큰 검증 성공
[백채널 로그아웃] 세션 탐색 결과
[백채널 로그아웃] 세션 파기 완료
[백채널 로그아웃] 처리 완료
주의:
- raw
logout_token전체를 로그에 남기지 않습니다. - access token, refresh token, cookie raw value도 남기지 않습니다.
테스트 체크리스트
기본 성공 시나리오
- PKCE RP 로그인
- callback 후
sid/sub -> rpSessionId매핑 생성 확인 - UserFront에서
세션 종료 - Baron이 RP의
Back-Channel Logout URI로 POST - RP가
logout_token검증 성공 - RP 세션 파기 성공
- 보호 페이지 접근 시 비로그인 상태 확인
확인 포인트
- devfront에
Back-Channel Logout URI가 실제 저장됐는가 - Baron backend가 해당 URI에 실제로 도달 가능한가
- RP 로그에
요청 수신과토큰 검증 성공이 찍히는가 - 세션 스토어에서 실제 세션이 삭제됐는가
SID Claim Required=true일 때와false일 때 결과가 의도대로 다른가
구현 예시 구조
Node.js/Express 기준 최소 구조 예시는 다음과 같습니다.
GET /login
GET /callback
GET /profile
GET /logout
POST /backchannel-logout
내부 저장 예시:
sidToSessionIds: Map<string, Set<string>>
subToSessionIds: Map<string, Set<string>>
sessionIdToBinding: Map<string, { sid: string, sub: string }>
실제 분리 예시는 아래 데모 코드를 참고할 수 있습니다.
- 백채널 로그아웃 모듈:
https://gitea.hmac.kr/kyy/pkce-login-demo/src/branch/main/backchannel-logout.js - 데모 앱 엔트리포인트:
https://gitea.hmac.kr/kyy/pkce-login-demo/src/branch/main/app.js
이 데모는:
- callback 이후
registerSessionBinding()으로sid/sub -> sessionId를 등록 POST /backchannel-logout에서handleBackchannelLogout를 그대로 연결- 로컬
/logout또는 세션 정리 시removeSessionBinding()호출
구조로 동작합니다.
자주 생기는 문제
1. localhost로는 안 되는데 입력은 저장됨
입력 validation을 통과하는 것과 Baron backend가 실제로 그 주소에 도달하는 것은 다릅니다.
예:
http://localhost:3333/backchannel-logout
이 값은 backend 컨테이너 기준으로는 자기 자신을 가리킬 수 있습니다. Docker 환경에서는 Docker 서비스명 또는 사설 IP를 사용해야 할 수 있습니다.
2. sid가 로그인 시 값과 다름
실제 운영에서는 logout_token.sid가 RP가 저장한 sid와 항상 같다고 가정하면 안 됩니다.
따라서:
sid우선subfallback
구현을 권장합니다. 다만 보안 정책상 SID Claim Required=true를 선택한 경우에는 fallback 없이 sid만 사용해야 합니다.
3. 순수 frontend-only PKCE인데 endpoint를 만들 수 없음
그 경우는 Back-Channel Logout 자체를 구현할 수 없습니다. 최소한 logout 수신용 서버 컴포넌트를 추가해야 합니다.
로직 흐름
sequenceDiagram
autonumber
participant Browser as 브라우저
participant RP as PKCE RP
participant Baron as Baron SSO
participant Store as 세션 스토어
Browser->>RP: GET /login 호출
RP->>Browser: Baron authorize endpoint로 리다이렉트
Browser->>Baron: Authorization Code + PKCE 로그인
Baron->>Browser: /callback?code=... 으로 리다이렉트
Browser->>RP: GET /callback 호출
RP->>Baron: code_verifier 포함 token 요청
Baron-->>RP: ID Token / Access Token 반환
RP->>Store: RP 세션 생성
RP->>RP: registerSessionBinding(sessionId, sid, sub)
RP-->>Browser: 로그인 완료 응답
Browser->>Baron: UserFront 또는 연동 서비스에서 세션 종료
Baron->>RP: POST /backchannel-logout (logout_token)
RP->>Baron: Back-Channel JWKS로 logout_token 검증
Baron-->>RP: 서명 / issuer / audience 검증 기준 제공
RP->>RP: sid 또는 sub로 sessionId 탐색
RP->>Store: destroy(sessionId)
RP->>RP: removeSessionBinding(sessionId)
RP-->>Baron: 200 OK
Browser->>RP: GET /profile 호출
RP-->>Browser: 루트 리다이렉트 또는 비로그인 응답
권장 결론
PKCE RP에서 Back-Channel Logout을 쓰려면, 다음 원칙을 따르십시오.
- PKCE 로그인 플로우는 그대로 유지
- logout 수신용 서버 endpoint 별도 구현
sid와sub를 모두 저장- 세션 스토어에서 직접 세션 파기
- 로컬 개발 시 Baron backend가 도달 가능한 URI를 사용
이 다섯 가지가 갖춰져야 Baron의 원격 세션 종료가 RP 로컬 세션 종료까지 이어집니다.