Skip to content

RP 통합 테스트 가이드

logi 를 처음 붙이는 RP 가 production 으로 가기 전, OAuth 흐름과 Webhook · merge 까지 모두 동작하는지 확인하기 위한 운영 가이드. 마케팅 문구가 아니라 "출시 전에 이걸 안 보면 새벽에 깬다" 수준의 체크리스트.

테스트 환경 옵션

logi 는 현재 별도 sandbox host 를 운영하지 않습니다. 모든 통합 검증은 production endpoint(https://api.1pass.dev) 의 테스트 전용 RP 등록으로 수행합니다.

  • 검증은 production IdP 에 별도의 테스트 OauthApplication 을 등록해 진행 — production 트래픽과 격리됨
  • 테스트 RP 에는 localhost, *.ngrok-free.app, 사내 staging 도메인 등 비공개 URL 을 redirect_uris 로 등록 가능 (HTTPS 또는 http://localhost 만 허용)
  • 결제 · 계정 삭제 · 의심 로그인 lockout 등 비가역 동작은 테스트 RP 전용 계정으로 재현

Self-hosted 개발 모드 미제공

1pass 는 docker-compose up 으로 띄울 수 있는 로컬 IdP 를 제공하지 않습니다. RP 단독 통합 작업 시에도 항상 production IdP 의 테스트 RP 를 사용하세요.

테스트용 자격증명 발급

  1. CLI 또는 개발자 포털에서 테스트 RP 등록name[test] prefix 권장 (예: [test] ainote staging)
  2. redirect_uris 에 staging/localhost URL 등록 — redirect_uri 는 완전 일치 이므로 trailing slash · 대소문자까지 정확히
  3. webhook_url 은 ngrok / Cloudflare Tunnel 로 외부 노출된 staging endpoint 사용 — 로컬 머신을 직접 등록할 수는 없음
  4. 발급되는 값:
    • client_id (logi_xxx...)
    • client_secret (confidential client 만 — 1회 노출, 즉시 staging secret manager 에 저장)
    • 초기 webhook signing key (kid + plaintext secret) — 마찬가지로 1회 노출

테스트 RP 와 production RP 는 반드시 다른 client_id / webhook secret 으로 분리하세요. 공유하면 staging 에서 회전한 키가 production 까지 영향을 줍니다.

흐름별 검증 체크리스트

OAuth Authorization Code + PKCE

production 직전 최소한 다음 케이스를 직접 수행:

  • [ ] 첫 로그인state · code_challenge 생성 → authorize → callback → /oauth/token 교환 → access_token 으로 /oauth/userinfo 200 응답
  • [ ] 재로그인 (동일 사용자) — 동일 sub 가 반환되는지. aud 가 본인 client_id 와 정확히 일치하는지
  • [ ] scope 추가 요청 — 기존 동의보다 넓은 scope 요청 시 동의 화면이 다시 표시되는지
  • [ ] 사용자 거부 — authorize 화면에서 거부 → callback 의 error=access_denied 처리 → 사용자에게 친화적 메시지
  • [ ] 만료된 access tokenexp 지난 토큰으로 API 호출 시 401, refresh 후 정상화
  • [ ] 잘못된 aud — 다른 RP 의 토큰을 본인 백엔드에 보내면 반드시 401 반환 (검증 누락 회귀 방지)
  • [ ] JWKS 캐시 강제 갱신kid 미스매치 시 1회 강제 refetch 후 재검증되는지 (JWKS 캐시 정책)

scripts/verify-rp.sh (repo 루트) 는 위 중 /login 노출 · authorize URL 파라미터 · code_challenge_method=S256 · AASA 매칭을 비파괴로 한 번에 확인합니다. PR 전 최소 1회 돌릴 것.

Webhook 수신

  • [ ] Signature 검증 — 두 형식 모두 — PLAN-L (t=<ts>,kid=<kid>,v1=<hex>) 과 legacy (sha256=<hex> + X-Logi-Timestamp 헤더) 둘 다 검증 통과. 두 형식이 공존하는 이유와 verifier 예시는 Webhook 서명 검증 참고
  • [ ] Replay 거부 — PLAN-L 은 t= 값, legacy 는 X-Logi-Timestamp 가 현재 시각과 ±5분을 벗어나면 거부
  • [ ] IdempotencyX-Logi-Event-Id (event_id) 기준 dedup. 동일 ID 로 두 번 들어오면 두 번째는 200 만 응답하고 부수효과 무시
  • [ ] 순서 보장 없음user.mergeduser.created 보다 먼저 도착해도 처리되는지 (logi outbox 는 최선순서, 엄격한 ordering 보장 안 함)
  • [ ] Grace window 동안 두 키 공존키 회전 직후 신 · 구 두 kid 모두 검증 통과해야 함
  • [ ] Webhook timeout — 10초 안에 200 응답 못 하면 logi 가 retry — 멱등성 없으면 중복 처리됨

Account Merge

자세한 시나리오는 Account Merge 개요Merge Idempotency 참조.

  • [ ] T2 (cross-provider, 동일 이메일) — Apple SSO 로 가입 → 동일 이메일로 Google SSO 로 재로그인 → 자동 merge 발생 후 user.merged webhook 수신
  • [ ] T3 (OTP 기반) — 이메일 OTP 로 두 anonymous 계정을 같은 사람으로 묶기
  • [ ] 12.3 session_token 기반 merge — RP 측 session 으로 활성 RP 측 user 와 logi survivor 의 매핑
  • [ ] T1 (device-link) 은 실기기 페어가 필요해 자동화하기 어려움 → 수동 검증만 권장
  • [ ] 모든 merge 후 RP 측에서 survivor_canonical_submerged_sub 기준으로 canonical_user_ids 해상도가 올바른지

자주 만나는 함정

증상원인해결
invalid_grant (token 교환)redirect_uri 가 등록값과 1글자 다름 (trailing /, scheme, 대소문자)등록 URI 와 코드의 URI 를 hex dump 로 비교
invalid_grant (code_verifier_mismatch)challenge ↔ verifier session 분실. cookie SameSite=Strict + cross-site redirect 충돌 흔함challenge 를 server-side session 또는 signed state 에 보관
JWT exp 검증 실패 (정상 발급 직후)서버 시계 skew >60s — 특히 Docker host clock driftchrony / NTP 검증, JWT 라이브러리의 clockTolerance 옵션 사용
Webhook 검증 실패 (서명 불일치)request body 를 파싱 후 재직렬화 한 값으로 HMAC 계산반드시 raw body 사용 (상세)
Webhook Content-Type 충돌Rails 의 parameter_wrapping / Express 의 body-parser 가 body 를 소비webhook endpoint 전용 라우트에서 raw body middleware 등록
aud mismatch다중 client_id 환경에서 토큰을 잘못된 백엔드로 라우팅aud == process.env.LOGI_CLIENT_ID 단정
Universal Link 미동작 (iOS)AASA 파일이 /oauth/authorize paths 누락curl https://api.1pass.dev/.well-known/apple-app-site-association 확인

CI 통합

logi 통합 테스트는 두 갈래로 나눠 운영하는 것이 안전합니다.

1) Smoke test (실제 IdP 호출) — PR 마다 또는 deploy 후

yaml
# .github/workflows/logi-verify.yml
name: logi RP verify
on: [pull_request, deployment_status]
jobs:
  verify:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run RP verification
        run: ./scripts/verify-rp.sh https://staging.example.com

비파괴 — /login 페이지 GET 과 AASA fetch 만 수행. authorize URL 의 client_id · code_challenge_method=S256 박혀 있는지 확인.

2) 단위 테스트 (mock IdP) — 매 commit

  • JWT 검증 로직: 테스트용 RSA 키 쌍으로 발급한 토큰을 nock / WebMock 으로 JWKS 응답 stub 한 뒤 검증
  • Webhook 검증: 알려진 (secret, body, ts) 조합에 대해 expected signature 가 일치하는지 fixture test
  • Refresh / revoke: /oauth/token, /oauth/revoke 응답을 stub

실 IdP 를 매 commit 마다 때리면 rate limit 에 걸립니다 — secret rotation 같은 비가역 동작은 절대 CI 에서 자동화하지 마세요.


logi 내부 — RP 통합 spec 패턴 (logi 본 코드베이스 기여자용)

logi 서버 자체에 RP 통합 회귀 spec 을 추가하는 경우:

1. 디렉토리

spec/integrations/ 에 두면 RSpec 의 type 자동 매칭이 안 됨. 명시 필요:

ruby
RSpec.describe "krx_listing RP integration", type: :request do
  # ...
end

2. Oauth::KeyStore stub (RAILS_MASTER_KEY 없이도 실행)

JWT 발급은 credentials 의 oauth_jwt.keys 가 필요. CI / 로컬에서 master key 없이도 결정적으로 통과시키려면 RSA 키페어 생성 후 stub:

ruby
before do
  rsa = OpenSSL::PKey::RSA.generate(2048)
  allow(Oauth::KeyStore).to receive(:active_kid).and_return("test-kid")
  allow(Oauth::KeyStore).to receive(:private_key).and_return(rsa)
  allow(Oauth::KeyStore).to receive(:public_keys)
    .and_return([ { kid: "test-kid", key: rsa.public_key } ])
end

JWKS 노출 정확성 자체는 spec/requests/oauth/jwks_spec.rb 가 책임 — 통합 spec 에선 토큰 발급 흐름만 검증.

3. PKCE 테스트 벡터

RFC 7636 §B.1.1 표준 벡터 사용 (다른 oauth spec 들과 일치):

ruby
let(:verifier)  { "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" }
let(:challenge) { "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM" }

4. app 이름 충돌 회피

let(:app) 은 Rack app 헬퍼와 충돌해 NoMethodError: undefined method 'call' for OauthApplication 으로 죽음. let(:rp_app) 같은 다른 이름 사용.

5. 모범 spec

  • spec/integrations/krx_listing_rp_integration_spec.rb — Web↔Web, App↔Web, pairwise-sub 격리, post-login fallback non-hijack 회귀 가드
  • spec/requests/oauth/refresh_token_rotation_spec.rb — refresh rotation + reuse detection
  • spec/requests/oauth/redirect_uri_strictness_spec.rb — exact-match
    • prefix-trap / fragment / userinfo-injection 거부

MIT License · Identity가 제품의 신뢰를 만듭니다.