diff --git a/docs/pkce-flow-stages.md b/docs/pkce-flow-stages.md new file mode 100644 index 0000000..9aca3e3 --- /dev/null +++ b/docs/pkce-flow-stages.md @@ -0,0 +1,15 @@ +# Authorization Code + PKCE stages + +1. SPA가 매 로그인마다 고엔트로피 `code_verifier`를 생성한다. +2. SHA-256과 Base64URL로 `code_challenge`를 만든다. +3. authorization request에는 challenge와 `S256`만 전송한다. +4. redirect의 code와 저장해 둔 state를 대조한다. +5. token request에 원래 verifier를 보내 code를 교환한다. +6. verifier/state/code는 한 번 사용한 뒤 메모리에서 제거한다. + +Keycloak client는 public client이며 implicit와 password grant를 끄고 S256을 +강제한다. PKCE는 악성 redirect endpoint가 code만 가로챘을 때의 교환을 막지만, +SPA 실행 컨텍스트를 장악한 XSS 자체를 막지는 않는다. + +`verify-pkce-flow-stages.sh`는 Web Crypto 단위 테스트, realm client 계약, +authorization/token 요청의 필드를 함께 검사한다. diff --git a/scripts/verify-pkce-flow-stages.sh b/scripts/verify-pkce-flow-stages.sh new file mode 100755 index 0000000..330f919 --- /dev/null +++ b/scripts/verify-pkce-flow-stages.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env sh +set -eu + +npm --prefix frontend ci +npm --prefix frontend test + +jq -e ' + .clients[] + | select(.clientId == "spa-public") + | .publicClient == true + and .standardFlowEnabled == true + and .implicitFlowEnabled == false + and .directAccessGrantsEnabled == false + and .attributes["pkce.code.challenge.method"] == "S256" +' keycloak/import/keycloak-patterns-realm.json >/dev/null + +rg -q 'response_type: "code"' frontend/src/app.js +rg -q 'new InMemoryWebStorage' frontend/src/app.js +rg -Fq 'cryptoApi.subtle.digest(' frontend/src/pkce.js +rg -Fq '"SHA-256"' frontend/src/pkce.js +echo "PKCE stages verified: verifier -> S256 challenge -> code -> verifier exchange"