Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1c2d05ae9 | ||
|
|
7916455ce5 | ||
|
|
55a99b8147 | ||
|
|
2765f5d109 |
@@ -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 요청의 필드를 함께 검사한다.
|
||||
@@ -0,0 +1,16 @@
|
||||
# Refresh token rotation contract
|
||||
|
||||
Realm 설정은 refresh token revoke를 활성화하고 재사용 허용 횟수를 0으로 둔다.
|
||||
SPA는 새 token set을 받은 즉시 이전 refresh token을 폐기한다.
|
||||
|
||||
실제 AP1 E2E는 다음을 구분한다.
|
||||
|
||||
- RT1로 refresh하면 RT2가 새로 발급된다.
|
||||
- 이미 소비한 RT1 재사용은 성공하면 안 된다.
|
||||
- 재사용 탐지 후 RT2까지 무효화할지는 Keycloak realm 정책과 동시 요청
|
||||
상황에 따라 관찰한다.
|
||||
- refresh token revoke 이후 refresh는 실패한다.
|
||||
- 이미 발급된 stateless access JWT는 `exp`까지 유효할 수 있다.
|
||||
|
||||
`verify-refresh-token-rotation-contract.sh`는 realm과 E2E assertions의 계약을
|
||||
빠르게 확인한다. 실제 token 수명주기 재현은 `verify-pattern1.sh`가 수행한다.
|
||||
Executable
+21
@@ -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"
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
jq -e '
|
||||
.revokeRefreshToken == true
|
||||
and .refreshTokenMaxReuse == 0
|
||||
and .accessTokenLifespan <= 300
|
||||
' keycloak/import/keycloak-patterns-realm.json >/dev/null
|
||||
|
||||
rg -Fq 'assert.notEqual(rotated.refresh_token, secondTokenSet.refresh_token)' \
|
||||
e2e/pattern1.mjs
|
||||
rg -Fq 'a consumed refresh token must not be accepted again' e2e/pattern1.mjs
|
||||
rg -Fq 'refresh revoke is not an immediate deny-list' e2e/pattern1.mjs
|
||||
|
||||
echo "Refresh rotation/reuse/revocation contract verified"
|
||||
echo "Run ./scripts/verify-pattern1.sh for the destructive live-token E2E"
|
||||
Reference in New Issue
Block a user