Scope 레퍼런스
Scope는 공백 구분 문자열입니다 (profile email phone, 콤마 ❌).
표준 Scope
| scope | userinfo 반환 필드 | 비고 |
|---|---|---|
profile | sub, email, email_verified, identity_verified_level | 기본 신원 |
email | sub, email, email_verified | profile의 부분집합 |
phone | sub, phone_number, phone_number_verified | Phase 2에서 실제 필드 추가 예정 |
openid | id_token 발급 + sub | OpenID Connect 1.0 활성화 |
identity_verified_level
logi는 실명/주민번호를 절대 보유하지 않습니다. 대신 정수 플래그만 제공:
0unverified (기본)1email_verified (logi 자체, magic link — Phase 2)2phone_verified (logi 자체 — Phase 2)3sp_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를 지정합니다:
{
"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 발생 여부를 정기 체크하세요. 발견 시:
- CLI 로
allowed_scopes업데이트:bashlogi apps edit <id> --add-scope phone - 또는 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 예시
{
"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개씩 발사
운영자가 직접 보기
# 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}"
endPhase 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/1.1 200 OK
Content-Type: application/json
X-Logi-Scope-Drift: address,phone
{ "access_token": "...", "token_type": "Bearer", ... }RP 측 처리 예시:
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.
end2) 개발자 대시보드 배지
- 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 실행. 다음 모두 충족 시 발사:
webhook_fired_at <= 7.days.ago(초기 알림 발사 ≥ 7일 경과)escalated_at IS NULL(아직 escalation 안 됨)last_seen_at > webhook_fired_at(drift 가 진행 중 — RP 가 안 고쳤음)
발사 후 escalated_at 세팅 → 영원히 1회만 발사.
{
"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 화면에 "필수" 배지가 붙고, 거부 시 "이 정보 없이는 서비스 이용 불가" 메시지가 표시됩니다.
# 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 (요청 ≠ 응답 가능 — 사용자가 일부만 동의한 경우).