Skip to content

Scope 레퍼런스

Scope는 공백 구분 문자열입니다 (profile email phone, 콤마 ❌).

표준 Scope

scopeuserinfo 반환 필드비고
profilesub, email, email_verified, identity_verified_level기본 신원
emailsub, email, email_verifiedprofile의 부분집합
phonesub, phone_number, phone_number_verifiedPhase 2에서 실제 필드 추가 예정
openidid_token 발급 + subOpenID Connect 1.0 활성화

identity_verified_level

logi는 실명/주민번호를 절대 보유하지 않습니다. 대신 정수 플래그만 제공:

  • 0 unverified (기본)
  • 1 email_verified (logi 자체, magic link — Phase 2)
  • 2 phone_verified (logi 자체 — Phase 2)
  • 3 sp_verified (SP가 NICE/KCB 등으로 실명인증 후 보고 — Phase 2)

제휴사는 이 정수로 게이팅만 수행하고, 실제 실명 데이터는 자체 인증 서비스에서 관리합니다.

커스텀 Scope (Phase 2)

제휴사는 자신의 도메인에 맞는 커스텀 scope를 등록할 수 있습니다. 네임스페이스 필수:

krx_listing:reviewer_role
enterprise_x:tier
blog:post.write

형식: <namespace>:<key> — 콜론 1개 기준 prefix로 앱과 매칭.

서버 측에서는 User#custom_claims 가 jsonb로 {namespace: {key: value}} 구조로 저장되며, 해당 scope 요청 시 id_token/userinfo에 병합됩니다 (β1-4).

제휴사 등록 시 allowed_scopes

앱 등록 시 허용할 scope를 지정합니다:

json
{
  "oauth_application": {
    "name": "My App",
    "redirect_uris": ["https://app.example.com/cb"],
    "allowed_scopes": ["profile", "email"]
  }
}

사용자가 이보다 넓은 scope를 요청하면 — Phase 1 (2026-04-29) 정책 변경: 더 이상 hard reject 하지 않습니다. Scope drift 정책 참고.

Scope drift policy (2026-04-29~)

배경: 운영 중인 RP 가 코드에서 신규 scope (예: phone) 를 요청했는데 logi 등록 시 allowed_scopes 업데이트를 깜빡한 케이스. 이전엔 error=invalid_scope 로 hard reject 해서 사용자 로그인 자체가 막혔음. 95%+ 케이스가 악의가 아니라 단순 등록 누락이라, 사용자 flow 를 끊는 건 과잉.

동작

logi 가 받은 scope 요청을 다음과 같이 처리합니다:

케이스동작
모든 요청 scope 가 등록됨✅ 그대로 진행
일부 요청 scope 가 미등록 (allowed_scopes 에 없음)⚠️ 미등록 scope 는 silent drop + 서버 로그 + 등록된 subset 으로 consent 진행
모든 요청 scope 가 미등록invalid_scope hard reject (consent 화면이 빈 상태가 됨)
required: true scope 가 effective scope 에서 누락invalid_scope hard reject (RP 가 의도한 데이터 못 받음)

보안 보장

미등록 scope 가 silent drop 되므로 사용자는 미등록 scope 에 대한 consent 화면조차 보지 않습니다. RP 는 등록된 scope 의 토큰만 받습니다 — 등록 외 데이터로 권한 escalation 불가능.

RP 개발자 측 감지 방법 (Phase 1)

미등록 scope 요청은 logi 서버 로그에 기록됩니다:

[oauth] scope_drift app_id=4 client_id=logi_xxx dropped=phone,address kept=profile,email

본인 RP 의 client_id 로 grep 해서 drift 발생 여부를 정기 체크하세요. 발견 시:

  1. CLI 로 allowed_scopes 업데이트:
    bash
    logi apps edit <id> --add-scope phone
  2. 또는 SSH/rails console 에서:
    ruby
    app = OauthApplication.kept.find_by(name: "your_app")
    app.set_scopes!(["openid", "profile", "email", "phone"])

Phase 2 (2026-04-29~)

Webhook 이벤트 scope.drift_detected 발사 — 어떤 RP 의 어떤 scope 가 처음 drift 됐는지 즉시 알림. (Phase 3 grace period / dashboard / X-Logi-Scope-Drift 헤더는 별도 PR 예정.)

Webhook payload 예시

json
{
  "event_type": "scope.drift_detected",
  "application_id": 4,
  "payload": {
    "scope_name": "phone",
    "client_id": "logi_30ca68e5464b5456269b0502395285d9",
    "first_seen_at": "2026-04-29T01:30:00Z",
    "allowed_scopes": ["openid", "profile:basic", "email"],
    "message": "Your app requested an unregistered scope. logi dropped it and proceeded with the registered subset. Update allowed_scopes to include this scope, or stop requesting it."
  }
}

서명 검증은 다른 webhook 과 동일한 HMAC-SHA256 패턴: HMAC 서명 검증.

발사 규칙 (스팸 방지)

  • (app_id, scope_name) 페어 당 1회 — 같은 RP 가 같은 미등록 scope 를 1000번 요청해도 webhook 은 첫 1회만
  • 등록된 webhook URL 이 없으면 발사 안 함 (drift 자체는 DB 에 기록)
  • 한 요청에 미등록 scope 가 여러 개면 (phone address) → scope 별로 각각 1개씩 발사

운영자가 직접 보기

ruby
# Rails console
ScopeDriftRecord.includes(:oauth_application)
                .order(last_seen_at: :desc).limit(20).each do |r|
  puts "#{r.oauth_application.name.ljust(20)} #{r.scope_name.ljust(15)} #{r.occurrence_count}#{r.last_seen_at}"
end

Phase 3 (2026-04-29~)

Token 응답 헤더, 대시보드 배지, 7일 후 escalation webhook 모두 라이브.

설계 변경 노트 (Phase 1 약속 유지): Phase 3 의 원래 계획은 "7일 후 hard reject 자동 복귀"였으나, 이는 Phase 1 의 핵심 원칙 (사용자 flow 안 끊기) 와 정면 충돌. 사용자 flow 는 끝까지 보호하고 RP 측 압박만 단계적으로 강화하는 escalation 모델로 변경.

1) /oauth/token 응답 헤더 X-Logi-Scope-Drift

토큰 발급 시 최근 7일 내 drift 가 있으면 헤더로 echo. RP 서버 코드가 webhook 도착을 기다리지 않고 매 요청에서 감지 가능.

http
HTTP/1.1 200 OK
Content-Type: application/json
X-Logi-Scope-Drift: address,phone

{ "access_token": "...", "token_type": "Bearer", ... }

RP 측 처리 예시:

ruby
res = Net::HTTP.post_form(URI("#{ENV['LOGI_API_URL']}/oauth/token"), token_params)
if drift = res["X-Logi-Scope-Drift"]
  Rails.logger.warn("[logi] scope drift detected: #{drift}")
  # → 슬랙 알림, Sentry 이벤트, etc.
end

2) 개발자 대시보드 배지

  • Apps 목록: drift 발생 RP 카드에 "Scope drift" 핀 + amber dot
  • Apps 상세: 별도 카드에 drift 테이블 (scope / 처음 발견 / 최근 / 횟수 / 상태)
  • 상태 컬럼:
    • 기록만: webhook URL 미설정 또는 발사 직전
    • webhook 전송: scope.drift_detected 발사됨
    • escalated: 7일 이상 미해결, scope.drift_unresolved 발사됨

3) Escalation webhook (scope.drift_unresolved)

매일 09:00 UTC ScopeDriftEscalationJob 실행. 다음 모두 충족 시 발사:

  1. webhook_fired_at <= 7.days.ago (초기 알림 발사 ≥ 7일 경과)
  2. escalated_at IS NULL (아직 escalation 안 됨)
  3. last_seen_at > webhook_fired_at (drift 가 진행 중 — RP 가 안 고쳤음)

발사 후 escalated_at 세팅 → 영원히 1회만 발사.

json
{
  "event_type": "scope.drift_unresolved",
  "payload": {
    "scope_name": "phone",
    "client_id": "logi_xxx",
    "first_seen_at": "2026-04-22T01:30:00Z",
    "last_seen_at": "2026-04-29T08:45:00Z",
    "occurrence_count": 1247,
    "allowed_scopes": ["openid", "profile:basic", "email"],
    "message": "This scope has been drifting for over 7 days and is still being requested. logi continues to drop it from consent (user flow is preserved). Please update allowed_scopes or remove the scope from your authorize requests."
  }
}

사용자 flow 가 절대 깨지지 않는 이유

단계사용자 영향
Phase 1 (silent drop)0 — 등록된 scope 만 consent 화면에 표시
Phase 2 (drift webhook)0 — 서버 로그 + RP webhook
Phase 3 (token header)0 — 응답 헤더, 클라이언트 코드는 무시 가능
Phase 3 (escalation webhook)0 — RP 운영자 알림

악의적 RP 가 미등록 scope 로 권한 escalation 시도해도, 사용자는 미등록 scope 의 consent 화면조차 보지 않으므로 데이터 leak 불가. 정직한 RP 가 등록 갱신 깜빡한 경우엔 사용자 로그인은 정상 진행 + RP 개발자에게 webhook + dashboard 배지 + token header 3중 신호.

이슈/요청: GitHub

required 마킹 (β1-6)

제휴사는 특정 scope를 필수로 표시할 수 있습니다. Consent 화면에 "필수" 배지가 붙고, 거부 시 "이 정보 없이는 서비스 이용 불가" 메시지가 표시됩니다.

ruby
# Rails console 예시
app.oauth_application_scopes.create!(
  oauth_scope: profile_scope, required: false
)
app.oauth_application_scopes.create!(
  oauth_scope: email_scope, required: true  # 필수
)

사용자가 필수 scope를 거부하면 access_denied + 정책 안내 페이지가 표시됩니다.

재인가 UX

사용자가 제휴사 A에 profile email 동의한 이후:

요청 scope동작
profile email (동일)UI 스킵 · 즉시 code 발급
profile (축소)UI 스킵 · 즉시 code 발급
profile email phone (확장)"NEW" 배지 + 추가 동의 요구
Consent revoke 후Consent 화면 다시 노출

이는 Google 로그인과 같은 UX입니다.

요청 방법

GET /oauth/authorize?...&scope=profile+email+openid&...
                              ^^^^^^^ ^^^^^ ^^^^^^
                              공백(+)으로 구분, URL encoding 시 %20 또는 +

응답 scope 필드는 실제 부여된 scope를 echo (요청 ≠ 응답 가능 — 사용자가 일부만 동의한 경우).

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