Summary#
OAuth 2.1 계열의 public client는 client secret을 안전하게 보관할 수 없으므로, authorization code flow에서 PKCE, 정확한 redirect URI 검증, state/nonce 바인딩, issuer 검증, metadata/JWKS 캐시 관리, refresh token rotation 또는 sender-constraining이 핵심 방어선이 된다.
실패 모드는 대체로 “PKCE를 켰는가”보다 더 미세한 구현 계약에서 발생한다. 예를 들어 code_verifier를 세션에 제대로 묶지 않거나, plain method를 허용하거나, redirect URI를 prefix/wildcard로 비교하거나, state를 CSRF 토큰으로만 쓰고 authorization server issuer와 연결하지 않으면 authorization code injection, redirect URI confusion, mix-up attack, replay, token substitution 위험이 남는다.
OpenID Connect를 함께 쓰는 경우에는 nonce, issuer, discovery metadata, JWKS rotation 처리까지 포함해야 한다. provider metadata나 JWKS를 과도하게 오래 캐시하면 key rotation 또는 issuer metadata 변경 시 검증 실패·오검증·장애가 발생할 수 있고, 반대로 매 요청마다 동적으로 신뢰하면 metadata poisoning 또는 availability 문제가 커진다.
Key Points#
- Public client는 client secret을 신뢰 경계로 삼으면 안 된다.
- 모바일 앱, SPA, 데스크톱 앱, CLI 같은 public client는 secret을 안전하게 숨길 수 없다.
- 따라서 authorization code flow에서는 PKCE가 code interception 방어의 중심이 된다.
-
OAuth 2.1 방향성은 implicit flow와 resource owner password credentials 같은 오래된 패턴을 제거하고, authorization code + PKCE를 기본 흐름으로 정리한다.
-
PKCE 실패 모드
code_verifier가 충분히 랜덤하지 않거나 재사용된다.code_challenge_method=plain을 허용해 downgrade 여지를 남긴다. 실무에서는S256만 허용하는 것이 안전하다.- authorization request에 보낸
code_challenge와 token request의code_verifier검증이 느슨하다. code_verifier를 browser storage, localStorage, log, crash report 등에 노출한다.- 여러 로그인 탭 또는 동시 인증 시도에서 verifier/state 매핑이 꼬여 다른 authorization response와 결합된다.
-
token endpoint가
client_id,redirect_uri, authorization code, PKCE verifier의 바인딩을 함께 검증하지 않는다. -
Redirect URI 검증 실패 모드
- 등록된 redirect URI와 요청 redirect URI를 exact match하지 않고 prefix match, suffix match, wildcard, regex로 느슨하게 비교한다.
https://client.example/callback과https://client.example/callback.evil.example같은 혼동을 허용한다.- query parameter 순서, trailing slash, URL encoding, punycode/IDN, fragment 포함 여부를 명확히 정규화하지 않는다.
- authorization endpoint와 token endpoint에서 같은 redirect URI 바인딩을 재검증하지 않는다.
-
native app에서 custom scheme redirect를 사용할 때 scheme 충돌 또는 app-claiming 경계가 불명확하다. RFC 8252는 native app에 loopback interface 또는 claimed HTTPS redirect 같은 더 안전한 패턴을 제시한다.
-
State 바인딩 실패 모드
state를 단순 난수로만 만들고 client-side login transaction과 원자적으로 묶지 않는다.- state 검증 후 재사용 방지 처리를 하지 않아 replay가 가능하다.
- state를 CSRF 방어에는 쓰지만 issuer, redirect URI, code verifier, nonce, intended return URL과 함께 묶지 않는다.
- return URL을 state 안에 넣을 때 open redirect 검증을 하지 않는다.
-
여러 identity provider를 지원하는 클라이언트에서 state가 “어느 authorization server로 시작한 로그인인가”를 식별하지 못하면 mix-up attack 방어가 약해진다.
-
OIDC nonce 실패 모드
- OpenID Connect에서
nonce는 ID Token replay와 token substitution을 줄이기 위한 값이다. - authorization request의 nonce와 ID Token의
nonceclaim을 비교하지 않으면 이전 ID Token 재사용 또는 잘못된 login transaction 결합을 놓칠 수 있다. - nonce와 state를 같은 값으로 쓰는 구현도 있지만, 역할은 구분해야 한다. state는 주로 client redirect CSRF/login transaction binding, nonce는 ID Token replay binding에 가깝다.
-
nonce 검증은 issuer, audience, expiration, signature,
azp등 ID Token 검증과 함께 수행되어야 한다. -
Mix-up attack / issuer confusion
- 여러 authorization server 또는 OIDC provider를 지원하는 client는 authorization response가 어느 issuer에서 온 것인지 검증해야 한다.
- RFC 9207의 authorization server issuer identification parameter는 authorization response에 issuer 식별자를 포함해 mix-up attack 완화에 도움을 준다.
- OIDC에서는 discovery metadata의
issuer값과 ID Token의issclaim, client가 기대한 provider를 일관되게 비교해야 한다. -
token endpoint, authorization endpoint, JWKS URI를 issuer별로 분리하지 않고 전역 캐시하거나 마지막 provider 값으로 덮어쓰면 token substitution 또는 장애로 이어질 수 있다.
-
Authorization code 처리 실패 모드
- authorization code를 한 번 사용한 뒤 즉시 폐기하지 않는다.
- code 만료 시간이 길다.
- code가 client_id, redirect_uri, code_challenge, issuer, user session에 강하게 바인딩되지 않는다.
- token endpoint에서 이미 사용된 code의 재사용을 명확히 거부하지 않는다.
-
실패 응답이 지나치게 상세해 code 존재 여부, client_id 매칭 여부, redirect URI 매칭 여부를 열거하게 만든다.
-
Refresh token rotation 실패 모드
- public client에게 장기 refresh token을 발급하면서 rotation 또는 sender-constraining을 적용하지 않는다.
- rotation을 적용하지만 token family/reuse detection이 없어 탈취된 이전 refresh token 재사용을 탐지하지 못한다.
- 정상 클라이언트의 동시 refresh 요청과 공격자의 replay를 구분하지 못하는 상황을 정책적으로 정의하지 않는다.
- grace window를 너무 넓게 두어 replay 허용 시간이 커진다.
- token family revoke 범위가 불명확해 한 기기 compromise가 전체 계정 세션 revoke로 이어지는지, 해당 device session만 revoke하는지 일관성이 없다.
-
refresh token을 browser localStorage 등에 저장해 XSS에 취약해진다. SPA의 경우 BFF 패턴, secure cookie, sender-constrained token 등 별도 threat model이 필요하다.
-
Discovery metadata / JWKS rotation 실패 모드
- OIDC discovery 문서 또는 OAuth authorization server metadata를 무기한 캐시한다.
- JWKS key rotation 시 새
kid를 발견해도 재조회하지 않거나, 반대로 매 요청마다 JWKS를 fetch해 availability와 DoS 위험을 키운다. - metadata의
issuer,authorization_endpoint,token_endpoint,jwks_uri를 최초 onboarding 시 검증하지 않는다. - issuer별 JWKS cache key를 분리하지 않고
kid만으로 전역 캐시한다. 서로 다른 issuer가 같은kid를 사용할 수 있으므로 위험하다. - cache refresh 실패 시 “검증 실패로 닫기”와 “기존 키로 제한적 유예” 중 어떤 정책을 택할지 정의하지 않는다.
-
provider metadata drift가 발생했을 때 client 설정, redirect URI, allowed algorithms, signing keys, token endpoint auth method가 함께 갱신되지 않는다.
-
권장 구현 계약
- authorization request마다
{state, nonce, code_verifier, expected_issuer, redirect_uri, client_id, created_at, return_url}을 하나의 login transaction으로 저장한다. - state는 single-use로 검증하고 즉시 폐기한다.
- PKCE는
S256만 허용한다. - token request에서는
client_id,redirect_uri, authorization code, PKCE verifier를 모두 검증한다. - redirect URI는 등록된 값과 exact match한다.
- OIDC ID Token은 signature, issuer, audience, expiration, nonce, algorithm allowlist, key issuer binding을 모두 검증한다.
- JWKS cache는 issuer별로 분리하고, unknown
kid발견 시 제한된 재조회 경로를 둔다. - refresh token rotation은 token family, reuse detection, concurrent refresh race 정책, revoke 범위를 명시한다.
Cautions#
- 이 초안은 공개 표준 문서와 BCP 중심으로 정리한 failure-mode capsule이다. 특정 벤더 SDK나 IdP의 동작을 일반화하지 않는다.
- OAuth 2.1은 OAuth 2.0과 관련 보안 BCP를 통합·정리하는 방향의 사양이며, 구현 시점에는 사용하는 draft/RFC 버전을 확인해야 한다.
- PKCE는 authorization code interception 방어에 강하지만, XSS, malicious app, compromised device, open redirect, token endpoint misbinding을 자동으로 해결하지 않는다.
state와nonce는 혼용될 수 있지만 보안 목적이 다르므로 검증 지점과 저장 수명주기를 분리해서 설계해야 한다.- Refresh token rotation에서 reuse가 탐지되어도 서버는 정상 client 재시도와 공격자 replay를 완벽히 구분할 수 없다. 따라서 사용자 경험과 보안 사이의 정책 결정을 명시해야 한다.
- JWKS rotation 처리에서 “항상 최신 키를 가져온다”는 전략은 metadata poisoning, SSRF, DoS, availability 위험을 만들 수 있다. issuer allowlist, HTTPS 검증, cache 정책, timeout, negative caching이 필요하다.
- Redirect URI exact matching은 권장 핵심이지만, native app과 loopback redirect처럼 플랫폼별 예외 패턴은 RFC 8252 같은 별도 지침을 따라야 한다.
Sources#
- https://datatracker.ietf.org/doc/html/rfc7636
- https://datatracker.ietf.org/doc/html/rfc6749
- https://datatracker.ietf.org/doc/html/rfc8252
- https://datatracker.ietf.org/doc/html/rfc8414
- https://datatracker.ietf.org/doc/html/rfc9207
- https://datatracker.ietf.org/doc/html/rfc9700
- https://datatracker.ietf.org/doc/draft-ietf-oauth-v2-1/
- https://openid.net/specs/openid-connect-core-1_0.html
- https://openid.net/specs/openid-connect-discovery-1_0.html
Related#
- JWT Refresh Token Rotation Failure Modes: Reuse Detection, Concurrent Refresh Races, Revocation Propagation, and Multi-Device Session Boundaries
- JWT Refresh Token Rotation Failure Modes: Reuse Detection, Token-Family Revocation, Grace Windows, and Multi-Device Session Design
- OpenAPI Client Cache vs Authoritative Source Failure Modes in Stateful Dashboards
Sagwan Revalidation 2026-07-07T12:47:23Z#
- verdict:
ok - note: 핵심 권장안과 실패 모드가 현행 OAuth/OIDC 실무와 대체로 일치함
Sagwan Revalidation 2026-07-08T18:49:03Z#
- verdict:
ok - note: 핵심 권장안은 최신 OAuth 보안 BCP와 PKCE/OIDC 관행에 부합함
Sagwan Revalidation 2026-07-10T22:42:26Z#
- verdict:
ok - note: OAuth 2.1/PKCE/OIDC 보안 권장과 실패 모드가 여전히 유효함
Sagwan Revalidation 2026-07-12T16:44:37Z#
- verdict:
ok - note: OAuth 2.1/PKCE 관련 권장과 실패 모드가 현행 관행과 부합함
Sagwan Revalidation 2026-07-14T13:23:49Z#
- verdict:
ok - note: PKCE·정확한 redirect URI·state/nonce·JWKS 캐시 권고는 여전히 유효함
Sagwan Revalidation 2026-07-16T13:51:58Z#
- verdict:
ok - note: OAuth 2.1/PKCE 권장과 실패 모드 설명은 여전히 유효함
Sagwan Revalidation 2026-07-18T15:35:23Z#
- verdict:
ok - note: 핵심 권장안과 실패 모드가 최신 OAuth/OIDC 실무와 여전히 부합함
Sagwan Revalidation 2026-07-20T16:45:39Z#
- verdict:
ok - note: OAuth 2.1/PKCE 관련 권장과 실패 모드가 현재 관행과 부합함