Skip to content

Anonymous Grants

logi 의 익명-우선 가입 흐름(v0.4) 은 사용자가 이메일이나 SSO 를 거치기 전에도 logi 앱을 사용할 수 있게 해줍니다. 다만 외부 RP 가 익명 user 에게 grant 를 발급받는 것은 기본적으로 차단 됩니다 — RP 는 logi 로부터 "검증된 subject" 를 받는다는 전제로 통합하기 때문입니다.

allow_anonymous_grants 는 RP 가 이 전제를 의식적으로 완화하기로 선택하는 opt-in 플래그입니다.

언제 켜는가

전형적 사용처:

  • Guest 모드 UX — 회원가입 전에 데모/체험을 제공하는 서비스. 익명 user 의 작업을 RP 가 일단 저장하고, 나중에 사용자가 SSO 로 promotion 했을 때 그 데이터를 연결.
  • 수집-우선 RP — RP 가 자체적으로 phone/email 같은 추가 식별 정보를 받아 subject 를 enrich 하는 경우. logi 는 "이 사람이 누구인지" 모르고 단지 "어떤 device 에서 왔는지" 만 보장.
  • B2B SDK / 임베드 — host app 이 자기 user 컨텍스트를 갖고 있고 logi 는 보조 서명만 제공하는 케이스.

켜지 않는 경우 (대부분의 일반 RP):

  • 결제, 의료, 금융, 또는 법적 식별이 필요한 서비스.
  • RP 가 logi sub 외에 추가 식별을 안 받는 단순 SSO 통합.

설정

콘솔의 RP 설정 페이지 → "고급" → "Allow anonymous grants" 토글. Admin API 로도 변경 가능합니다 — 단 일반 PATCH 가 아닌 전용 member action 을 사용해야 합니다 (admin 컨트롤러는 화이트리스트된 액션만 허용):

POST /api/v1/admin/applications/:id/edit_fields
{ "application": { "allow_anonymous_grants": true } }

/admin/oauth_applications/... 같은 prefix 는 존재하지 않습니다 (전부 /admin/applications/...). 인증은 admin JWT + step-up 입니다.

기본값은 false. 켜는 즉시 변경되며 기존 grant 에는 영향을 주지 않습니다.

익명 user 가 차단될 때의 응답

RP 가 allow_anonymous_grants=false (기본값) 상태에서 익명 user 가 consent 를 시도하면 logi 는 HTTP 403 으로 다음 body 를 돌려줍니다:

json
{
  "error": "anonymous_not_allowed",
  "error_description": "Tennis Bracket 은 식별된 logi 계정만 허용해요. 설정 → 계정 → Apple/Google 연결 또는 이메일 등록 후 다시 시도해주세요.",
  "requires_developer": false,
  "self_rp": false,
  "application_name": "Tennis Bracket",
  "remediation": {
    "action": "link_identity",
    "user_facing_label": "계정 설정 열기"
  }
}

각 필드의 용도:

필드용도
error_description사용자에게 보여줄 수 있는 한국어 안내. RP 이름이 박혀있음 — 차단 주체가 logi 가 아니라 해당 RP 의 정책임을 명시.
application_nameRP 이름만 단독으로 필요할 때 (예: 커스텀 카피 조합).
requires_developerself-RP (1pass 콘솔) 에서 console:manage 같은 developer 전용 scope 요청 시 true. 그 외엔 false.
remediation.actionlink_identity (Apple/Google/email 추가) 또는 promote_to_developer (개발자 모드 승급). 모바일 sheet 가 분기 처리.
remediation.user_facing_label"계정 설정 열기" CTA 버튼 라벨.

모바일/네이티브 RP 개발자

사용자에게 메시지를 그대로 노출하지 말고, application_name + remediation.action 을 조합해 자기 앱의 톤에 맞춰 다시 쓰는 것을 권장합니다. logi 가 돌려주는 한국어 카피는 안전한 fallback 입니다.

QR 로그인 (모바일 → 데스크톱) 도 동일 응답

데스크톱 브라우저가 띄운 QR 을 logi 앱이 스캔하고 POST /api/v1/oauth/qr/:id/approve 를 호출할 때, 익명 user 면 위와 동일한 anonymous_not_allowed shape 으로 403 을 돌려줍니다 (application_name + remediation). 단 QR flow 는 promotion.resume_token 을 포함하지 않습니다 — QR session 자체가 resume vehicle 이므로, 사용자가 promotion 을 끝낸 뒤 같은 session_uuid/approve 를 한 번 더 호출하면 그대로 이어집니다.

Just-In-Time (JIT) Promotion

403 응답에는 promotion 객체 가 함께 옵니다. 이 객체로 logi 앱(또는 logi SDK 를 임베드한 RP) 이 사용자를 OAuth dance 밖으로 던지지 않고 인라인으로 Apple/Google/email 등록을 받아서 같은 흐름을 이어갈 수 있습니다. RP 입장에서는 차이 없음 — 평소처럼 ?code=&state= 콜백을 받습니다.

응답에 추가되는 promotion 객체

json
{
  "error": "anonymous_not_allowed",
  "error_description": "...",
  "application_name": "Tennis Bracket",
  "requires_developer": false,
  "self_rp": false,
  "remediation": { "action": "link_identity", "user_facing_label": "계정 설정 열기" },
  "promotion": {
    "required": true,
    "reason": "identified_account",
    "methods": [
      { "kind": "apple",          "label": "Apple 로 가입",  "start_url": "/api/v1/me/connected_identities" },
      { "kind": "google",         "label": "Google 로 가입", "start_url": "/api/v1/me/connected_identities" },
      { "kind": "email_password", "label": "이메일·비밀번호로 가입", "start_url": "/api/v1/me/emails" }
    ],
    "resume_token": "eyJhbGciOiJIUzI1NiJ9...",
    "resume_endpoint": "/api/v1/oauth/authorize/resume",
    "resume_expires_in": 300
  }
}
필드의미
promotion.reasonidentified_account (식별 계정만 받음) 또는 developer_role (개발자 모드 필요 — self-RP console:manage 시)
promotion.methods[]모바일 sheet 가 렌더할 등록 옵션. kind 로 분기. developer_role 일 때는 email_password 만 옴 (개발자 모드는 검증된 이메일이 필요)
promotion.resume_tokenlogi 가 사인한 single-use, 5분 TTL JWT. 원래의 client_id / redirect_uri / state / code_challenge / scope 를 모두 묶음
promotion.resume_endpoint등록 완료 후 POST 할 경로 (/api/v1/oauth/authorize/resume)
promotion.resume_expires_in초 단위 (300 = 5분). 안에 못 끝내면 사용자가 다시 시작해야 함

흐름 (시퀀스)

mermaid
sequenceDiagram
  participant App as RP 앱
  participant Logi as logi (IdP)
  participant Apple as Apple SDK

  App->>Logi: POST /api/v1/oauth/authorize (anonymous PAK)
  Logi-->>App: 403 + promotion { resume_token, methods }
  Note over App: 인라인 promotion sheet 노출
  App->>Apple: id_token 요청
  Apple-->>App: id_token
  App->>Logi: POST /api/v1/me/connected_identities<br/>{ provider:"apple", identity_token, raw_nonce }
  Logi-->>App: 201 { connected_identities: [...] } (user now identified)
  App->>Logi: POST /api/v1/oauth/authorize/resume<br/>{ resume_token }
  Logi-->>App: 201 { code, state, redirect_uri }
  App->>RP: open(redirect_uri?code=&state=)

POST /api/v1/oauth/authorize/resume

Auth: Bearer PAK (resume_token 발급받은 같은 사용자).

Body: { "resume_token": "<JWT>" }

응답:

StatusBody의미
201{ code, state, redirect_uri }성공 — RP 콜백으로 코드 전달 가능
400{ error: "invalid_request" }resume_token 누락
401{ error: "unauthenticated" }PAK 없음
403{ error: "resume_user_mismatch" }resume_token 의 user ≠ 현재 PAK 의 user. 계정이 바뀌었음 — 처음부터 다시
403{ error: "developer_required" }등록은 됐지만 개발자 모드까지 필요한 scope 였음
422{ error: "promotion_incomplete" }토큰은 멀쩡하지만 user.anonymous 가 여전히 true. 등록 단계가 끝나지 않았음
422{ error: "resume_token_expired" }5분 초과
422{ error: "resume_token_already_used" }한 번 redeem 했음
422{ error: "invalid_resume_token" }서명 불일치 / 형식 깨짐 / 만료 외 사유

보안 모델

  • 단일 사용: resume_token 은 한 번 redeem 되면 jti 가 cache 에 마킹되어 재사용 차단. 크래시 로그/스크린샷에 노출되어도 두 번 못 씀.
  • 사용자 바인딩: 토큰 발급 시점의 user.id 가 페이로드에 박힘. 등록 중에 다른 계정으로 PAK 가 갈리면 resume_user_mismatch 로 fail-closed.
  • 서버 검증 파라미터만 재생: client 가 보내는 raw body 가 아니라 토큰 안의 canonical (logi 가 직접 parse 해서 박은) client_id / redirect_uri / state / scope / code_challenge 로 재생. code_challenge 변조로 grant 가로채기 불가능.
  • TTL 5분: 등록 흐름이 사실상 1분 안에 끝나는 것을 가정. 더 길면 사용자가 deep-link 헷갈려서 그냥 다시 시도하는 게 안전.

RP 입장에서는?

아무것도 바꿀 필요 없음. 평소처럼 /oauth/authorize?code=&state= 콜백 → /oauth/token 교환만 하면 됩니다. logi 가 익명 사용자의 promotion 을 OAuth dance 안에서 흡수해서 RP 에게는 항상 식별된 user 로 코드를 넘깁니다.

JIT promotion 후 발급되는 grant 의 audit log 에는 via: "jit_promotion_resume" 가 기록되어 운영자가 "어떻게 들어온 consent 인지" 추적할 수 있습니다.

익명 user 의 grant 가 어떻게 다른가

allow_anonymous_grants=true RP 가 익명 user 의 grant 를 받으면 userinfo 응답은:

json
{
  "sub":          "9182",
  "canonical_sub": "9182",
  "is_canonical": true,
  "anonymous":    true,
  "email":        "anon+abc123@1pass.internal",
  "email_verified": false,
  "linked_subs":  []
}

핵심 차이:

  • anonymous: true — RP 가 이 user 가 익명 임을 인식할 수 있음.
  • email 은 internal placeholder (anon+<hash>@1pass.internal) — 실제 이메일 아님.
  • email_verified: false.

RP 는 이 user 를 자기 도메인 모델에 저장할 때 "익명 placeholder" 표기를 해두는 것이 안전합니다.

익명 → identified promotion 시 동작

익명 user 가 나중에 Apple/Google SSO 로 promotion 되면 logi 가 자동으로:

  1. 같은 user.id 에 apple_sub 또는 google_sub 채움.
  2. anonymous: false 로 전환.
  3. provider 가 이메일을 제공했으면 email_address 를 실제 이메일로 교체.
  4. previously_anonymous: true claim 이 user 의 lifetime 내내 박힘.
  5. 다음 토큰 회전 시 RP 는 변경된 claim 을 받음.

이때 RP-side 에서 해야 할 일:

  • userinfo 의 anonymous 가 true → false 로 바뀐 것을 감지해서 자기 user row 를 "promoted" 로 마킹.
  • 익명 시절에 모은 데이터를 promoted user 의 view 에 그대로 유지.

webhook 으로 별도 이벤트가 emit 되지는 않습니다 — promotion 은 같은 user.id 의 단순 update 이고 통합이 아니기 때문입니다. RP 는 token 회전 시점에 인지해야 합니다.

익명 user 의 통합

익명 user 가 다른 user 에게 흡수되거나 다른 user 를 흡수하는 시나리오:

  • 익명 user 가 SSO promotion 도중 같은 이메일의 기존 user 와 매칭 → T2 트리거. 익명 user 가 흡수되어 사라지고 survivor 의 데이터로 통합. RP 는 user.merged 받음.
  • 사용자가 T3 로 익명 계정을 명시적으로 흡수 → 가능하지만 흔하지 않음. dual PoP 만 통과하면 동작.

흡수된 익명 user 의 데이터는 linked_user_id 로 남아 RP-side logi_identity_links 에 기록됩니다.

RP 운영 체크리스트 (권장)

allow_anonymous_grants=true 를 켠 RP 가 사용자 신뢰를 유지하기 위해 점검하면 좋은 항목들입니다. 법적 요구사항(개인정보처리방침 등) 은 각 RP 의 관할 법령에 따라 별도 검토가 필요합니다.

  • [ ] 익명 user 의 데이터를 별도 lifecycle 로 관리 (보존 기간, 익명 placeholder 정리 정책 등).
  • [ ] anonymous: true 인 user 에게는 결제·법적 책임이 따르는 작업을 노출하지 않거나 제한하는 편이 안전합니다.
  • [ ] promotion 시점 (anonymous: false 로 전환) 에 익명 시절 데이터가 자연스럽게 연결되도록 설계.
  • [ ] user.merged 수신 시 익명 user 의 데이터도 canonical 로 정상 합쳐지는지 사전 테스트.
  • [ ] 개인정보처리방침에 "익명 사용 + 후속 promotion 시 데이터 연결" 정책을 명시 (관할 법령 검토 권장).

보안 고려

  • 익명 grant 의 본인 확인 수단은 디바이스의 device_secret 하나입니다. 결과적으로 디바이스 보안 수준이 곧 grant 의 보안 수준이 되므로, 높은 보안 수준이 요구되는 작업에는 익명 grant 를 사용하지 않기를 권장합니다.
  • 익명 user 가 30일 유예 기간 내에 SSO 를 연결하지 않으면 PurgeUserJob 으로 hard-delete 됩니다. 이때 RP 는 user.grants_revoked 이벤트를 받습니다. 사용자가 grace 기간 안에 복구하려면 POST /api/v1/account_recoveries 흐름을 사용합니다.
  • 운영자는 oauth_applications.allow_anonymous_grants 가 true 인 RP 목록을 주기적으로 점검하기를 권장합니다.

관련 문서

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