OAuth와 OIDC 세션 설계: 로그인 성공 이후가 더 중요하다
토큰 수명, refresh token 보관, 로그아웃, 권한 변경 반영을 중심으로 인증 시스템 운영 기준을 정리했다. OAuth·OIDC·Session 관점에서 기본 개념부터 구현 순서, 검증 방법, 문제가 생겼을 때 되돌리는 절차까지 설명한다.
핵심 요약
OAuth는 API 권한 위임, OIDC는 사용자 인증을 위한 계층이며 로그인 성공만으로 세션 설계가 끝나지 않는다. 짧은 access token, 안전한 refresh token 회전, 권한 변경 전파, 서버 측 세션 폐기, 기기별 로그아웃, issuer·audience·nonce 검증을 하나의 수명주기로 운영해야 한다.
OAuth와 OIDC 구현은 로그인 화면에서 콜백을 받고 사용자 정보를 표시하면 완료된 것처럼 보인다. 실제 사고는 그 이후에 많이 발생한다. 퇴사자의 권한이 오래 남고, 도난된 refresh token이 계속 새 access token을 만들며, “로그아웃”을 눌러도 API 토큰은 만료될 때까지 유효하고, 다른 서비스용 토큰이 잘못된 API에서 받아들여진다.
인증 시스템을 안전하게 운영하려면 브라우저 세션, OIDC ID token, OAuth access token, refresh token, API 권한을 서로 다른 자산으로 구분해야 한다. 각각의 발급자, 대상, 수명, 저장 위치, 폐기 방법이 다르다.
OAuth와 OIDC의 역할을 먼저 구분한다
- OAuth 2.0은 클라이언트가 사용자를 대신해 자원 서버 API에 접근할 권한을 얻는 프레임워크다.
- OpenID Connect는 OAuth 위에 사용자 인증과 identity claim을 정의한다.
- ID token은 클라이언트가 인증 결과를 확인하기 위한 것이며 일반 API 호출용 access token으로 쓰지 않는다.
- access token은 특정 resource server와 scope를 위해 발급된다.
- refresh token은 새 access token을 얻는 장기 자격증명에 가깝다.
- 애플리케이션 세션 쿠키는 서비스가 자체 로그인 상태를 유지하는 수단이다.
한 토큰을 여러 역할에 재사용하면 audience 혼동과 과도한 수명이 생긴다. 토큰을 받는 각 구성요소는 “누가 발급했는가, 나를 대상으로 발급됐는가, 어떤 권한인가, 아직 유효한가”를 독립적으로 검증해야 한다.
세션 수명주기를 한 장으로 설계한다
인증 요청
→ authorization code + PKCE
→ ID/access/refresh token 발급
→ 서버 세션 또는 BFF에 보관
→ access token 갱신
→ 역할·계정 상태 변경 반영
→ 기기별 또는 전체 세션 폐기
→ 로그아웃·토큰 revocation
→ 감사·이상 탐지
RFC 9700: OAuth 2.0 Security Best Current Practice는 authorization code flow와 PKCE, redirect URI의 정확한 검증, refresh token 보호 등 현재의 보안 권고를 정리한다. 레거시 implicit flow나 password grant를 새 시스템의 기본값으로 선택하지 않는 것이 좋다.
access token은 짧고 대상이 분명해야 한다
자체 포함 JWT access token은 API가 매 요청마다 인증 서버에 조회하지 않아도 되지만, 발급 후 즉시 폐기하기 어렵다. 따라서 수명을 짧게 두고 다음을 검증한다.
iss: 신뢰한 authorization server인가aud: 이 API를 대상으로 발급됐는가exp,nbf,iat: 시간 조건이 유효한가- 서명 알고리즘과 키: 서버가 허용한 값인가
scope또는 권한 claim: 요청 작업에 충분한가- 사용자·클라이언트·테넌트 바인딩: 현재 리소스와 맞는가
JWT payload를 디코딩했다는 사실은 검증이 아니다. 서명, issuer, audience, 시간, 권한을 모두 검사한다. 알고리즘을 토큰 헤더에 맡기지 말고 서버에서 허용 목록을 고정한다.
권한이 민감하거나 즉시 폐기가 중요한 API는 opaque token과 introspection, 서버 측 세션 상태, 짧은 JWT에 추가 상태 확인을 조합할 수 있다. 모든 요청에 중앙 조회를 붙일 때의 가용성과 지연도 함께 설계해야 한다.
refresh token은 비밀번호처럼 다룬다
refresh token이 탈취되면 access token이 짧아도 공격자가 계속 갱신할 수 있다. 브라우저 JavaScript와 모바일 앱에 장기 토큰을 그대로 노출하지 않는 구조가 중요하다.
웹 애플리케이션
가능하면 Backend-for-Frontend가 토큰을 서버에 보관하고 브라우저에는 Secure, HttpOnly, 적절한 SameSite 속성의 세션 쿠키만 준다. 쿠키 기반 세션에는 CSRF 방어가 필요하다. 브라우저 저장소의 토큰은 XSS가 발생했을 때 읽힐 수 있다.
네이티브·모바일 앱
authorization code + PKCE를 사용하고 OS 보안 저장소를 활용한다. 앱 바이너리에 client secret을 넣어 기밀 클라이언트처럼 취급하지 않는다. 모바일 앱의 정적 키 위험은 모바일 API 키 서버 검증에서 더 자세히 다룬다.
회전과 재사용 탐지
refresh token rotation을 사용하면 갱신할 때 이전 토큰을 폐기하고 새 토큰을 발급한다. 이미 사용한 이전 토큰이 다시 나타나면 탈취 가능성을 의심하고 해당 토큰 패밀리 또는 세션을 폐기한다. 네트워크 재시도와 동시 요청으로 정상 충돌이 발생할 수 있으므로 짧은 유예, 원자적 상태 변경, 명확한 재인증 UX가 필요하다.
로그아웃은 여러 상태를 종료하는 절차다
사용자가 서비스에서 로그아웃해도 다음 상태가 남을 수 있다.
- 애플리케이션 세션 쿠키
- authorization server의 SSO 세션
- access token
- refresh token과 token family
- 다른 기기·브라우저 세션
- 연결된 API의 캐시와 장기 작업
따라서 “현재 기기 로그아웃”과 “모든 세션 로그아웃”을 구분한다. 현재 세션 쿠키를 폐기하고 refresh token을 RFC 7009 Token Revocation 엔드포인트 또는 공급자 기능으로 폐기한다.
자체 포함 access token은 즉시 무효화되지 않을 수 있으므로 짧은 수명, denylist, session version, introspection 중 위협 모델에 맞는 방식을 선택한다.
OIDC 제공자와 여러 relying party의 세션을 연동하려면 front-channel 또는 OIDC Back-Channel Logout 지원을 검토한다. 로그아웃 메시지의 issuer, audience, event claim, session ID를 검증하고 재전송을 안전하게 처리해야 한다.
권한 변경은 토큰 만료보다 빨리 반영돼야 한다
관리자 권한 회수, 계정 정지, 조직 이동, 비밀번호 재설정이 access token 만료까지 반영되지 않으면 공격 창이 생긴다. 다음 패턴을 조합한다.
- 민감 API에서 사용자·세션 상태를 서버 측으로 재확인
- 역할·정책 버전을 token claim에 넣고 현재 버전과 비교
- 권한 변경 이벤트로 세션과 refresh token family 폐기
- 관리자 작업에 step-up authentication 요구
- 고위험 작업은 최근 인증 시각과 기기 상태 확인
권한 claim을 수시간 캐시하면 회수 지연도 수시간이 된다. 캐시 TTL과 장애 시 동작을 보안 요구사항으로 명시한다.
redirect와 로그인 요청을 검증한다
로그인 시작 단계에서도 세션 혼동과 code 탈취를 막아야 한다.
- redirect URI를 와일드카드가 아닌 정확한 값으로 등록한다.
state를 로그인 요청과 세션에 바인딩해 CSRF를 방지한다.- OIDC
nonce를 요청별로 생성하고 ID token과 비교한다. - authorization code flow에서 PKCE를 사용한다.
- code는 한 번만, 짧은 시간, 해당 client와 redirect URI에서 사용한다.
- 다중 issuer 환경에서는 issuer mix-up을 방지한다.
- 로그인 완료 후 임의의 외부 URL로 redirect하지 않는다.
state에 원래 URL과 사용자 정보를 평문으로 넣고 서명 없이 신뢰하면 open redirect와 변조가 생긴다. 서버 세션에 보관하거나 무결성 보호된 짧은 토큰을 사용한다.
키와 discovery 실패를 정상 운영으로 다룬다
OIDC 공급자는 서명 키를 교체할 수 있다. JWKS를 캐시하되 알 수 없는 kid가 왔다고 무조건 외부 URL을 따라가면 안 된다. 고정된 issuer의 discovery와 JWKS URI만 사용하고, 갱신 빈도와 실패 시 정책을 정한다.
- 알려지지 않은
kid는 한 번의 제한된 refresh 후 실패 처리 - 이전 키와 새 키가 겹치는 회전 기간 지원
- discovery 문서와 JWKS 변경 감사
- 네트워크 장애 시 만료된 키 캐시를 얼마나 사용할지 결정
- issuer별 키 캐시를 분리해 혼동 방지
흔한 실패 모드
| 실패 | 결과 | 개선 |
|---|---|---|
| ID token을 API access token으로 사용 | 대상·권한 검증이 흐려짐 | 토큰 용도와 audience 분리 |
JWT 서명만 확인하고 aud 미검증 | 다른 서비스용 토큰 수용 | API별 정확한 audience 요구 |
| 브라우저 localStorage에 장기 refresh token | XSS 시 장기 세션 탈취 | BFF·HttpOnly 세션 또는 강한 저장 경계 |
| 로그아웃 시 쿠키만 삭제 | refresh token과 다른 기기 세션 유지 | 서버 세션·token family 폐기 |
| 역할 변경을 토큰 만료까지 기다림 | 회수된 권한이 계속 작동 | session version·상태 조회·이벤트 폐기 |
| redirect URI 와일드카드 | code가 공격자 경로로 유출 | 정확한 URI 등록 |
| refresh token 재사용 무시 | 복제된 토큰이 계속 갱신 | rotation과 reuse detection |
| 모든 인증 오류를 같은 로그로 남김 | 공격과 설정 오류 구분 불가 | issuer·aud·kid·세션·원인 구조화 |
탐지 신호
- 폐기된 refresh token 또는 이전 세대 token의 재사용
- 같은 token family가 짧은 시간에 서로 다른 지역·기기에서 사용됨
- 로그아웃·계정 정지 후 access token이 계속 성공하는 사건
iss,aud,azp,nonce,state불일치- 알 수 없는
kid, JWKS refresh 급증, 서명 검증 실패 - redirect URI 불일치와 authorization code 재사용
- 관리자 scope 요청·발급·사용량의 급격한 변화
- 한 계정의 refresh 빈도와 동시 세션 수 이상
- MFA·step-up 실패 후 민감 API 반복 호출
- 토큰 introspection·revocation 엔드포인트 장애
토큰 원문은 로그에 남기지 않는다. jti나 토큰 해시, client ID, subject의 내부 식별자, session ID, issuer, audience, 결과와 추적 ID만 최소한으로 기록한다.
운영 체크리스트
- OAuth 권한 위임과 OIDC 인증의 토큰 용도를 구분한다.
- 새 클라이언트는 authorization code + PKCE를 사용한다.
- redirect URI가 정확히 등록되고
state·nonce를 검증한다. - 모든 API가 issuer, audience, 시간, 서명, scope를 확인한다.
- access token 수명은 폐기 요구와 위험에 맞게 짧다.
- refresh token은 안전한 저장소에 있고 rotation을 사용한다.
- refresh token 재사용 시 token family를 폐기할 수 있다.
- 현재 기기와 모든 기기 로그아웃이 구분돼 있다.
- 계정 정지·역할 회수가 토큰 만료 전에 반영된다.
- JWKS 회전과 공급자 장애 시 동작을 시험했다.
- 토큰·code·cookie 원문이 로그와 URL에 남지 않는다.
- 세션 목록, 기기 해제, 최근 활동을 사용자가 확인할 수 있다.
관리자와 고위험 사용자에게는 피싱 저항성 MFA 로드맵을 적용하고, 인증 이후의 서비스 간 권한은 제로 트러스트 identity 경계와 연결한다. OAuth 연결을 통해 들어오는 외부 이벤트는 웹훅 재전송 방지 기준으로 별도 검증해야 한다.
최종 판단
안전한 로그인은 성공 콜백이 아니라 세션이 끝날 때까지 이어지는 수명주기다. access token을 짧고 대상별로 제한하고, refresh token을 회전·재사용 탐지하며, 로그아웃과 권한 회수를 서버 상태에 연결해야 한다. 각 토큰의 목적과 폐기 방법을 명확히 나누면 로그인 이후의 긴 공격 창을 줄일 수 있다.
전체 댓글 0개