Skip to content

Anonymous Grants (allow_anonymous_grants)

Source: oauth/anonymous-grants.md · Live: https://docs.1pass.dev/oauth/anonymous-grants LLM-sanitized: internal links absolutized, VitePress containers → admonitions, line numbers in the Jump-to Index reference this rendered file (1-indexed).

📍 Jump-to Index

  • L35-L45: ## 언제 켜는가
  • L46-L70: ## 설정
    • L50-L53: ### OTP step-up (재인증)
    • L54-L57: ### PAK / CLI / MCP 로는 토글 불가
    • L58-L70: ### Admin API (운영자 override)
  • L71-L106: ## 익명 user 가 차단될 때의 응답
  • L107-L196: ## Just-In-Time (JIT) Promotion
    • L111-L143: ### 응답에 추가되는 promotion 객체
    • L144-L163: ### 흐름 (시퀀스)
    • L164-L183: ### POST /api/v1/oauth/authorize/resume
    • L184-L190: ### 보안 모델
    • L191-L196: ### RP 입장에서는?
  • L197-L219: ## 익명 user 의 grant 가 어떻게 다른가
  • L220-L235: ## 익명 → identified promotion 시 동작
  • L236-L244: ## 익명 user 의 통합
  • L245-L254: ## RP 운영 체크리스트 (권장)
  • L255-L260: ## 보안 고려
  • L261-L267: ## 관련 문서

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 소유 개발자가 웹 콘솔에서 직접 켭니다 — 운영자에게 요청하거나 admin 이 대신 켜주는 흐름이 아닙니다. 개발자 콘솔 → 앱 편집 폼 → "익명 사용자 로그인 허용" 토글.

OTP step-up (재인증)

플래그를 바꾸려면 최근(5분 이내) OTP 재검증 이 필요합니다. 민감 설정이므로 세션이 살아 있어도 직전에 OTP 를 한 번 더 통과해야 토글이 저장됩니다. 단 OTP 미등록(unenrolled) 개발자는 차단되지 않습니다 — 기존의 관대한(lenient) 정책을 그대로 따릅니다.

PAK / CLI / MCP 로는 토글 불가

이 플래그는 웹 콘솔(및 admin iOS 앱) 전용입니다. PAK / CLI / MCP 같은 머신 credential 채널로는 변경할 수 없습니다 — 이 채널들은 interactive OTP step-up 을 수행할 수 없기 때문입니다. CLI 로 작업하던 개발자도 이 토글만큼은 웹 콘솔에서 켜야 합니다.

Admin API (운영자 override)

운영자가 admin iOS 앱(또는 admin API)으로 force ON/OFF 하는 경로는 여전히 사용할 수 있습니다. 단 일반 PATCH 가 아닌 화이트리스트된 전용 액션을 사용해야 합니다 (allow_anonymous_grantsEDITABLE_FIELDS 에 포함):

PATCH /api/v1/admin/applications/:id/edit_fields
{ "fields": { "allow_anonymous_grants": true }, "action_request_nonce": "<nonce>" }

요청 body 는 fields 키로 감쌉니다 (application 이 아님). /admin/oauth_applications/... 같은 prefix 는 존재하지 않습니다 (전부 /admin/applications/...). 인증은 admin JWT + step-up(action_request_nonce) 입니다.

기본값은 false. 익명 = 검증되지 않은 subject(sub) 를 받는다는 의미라서, 켜면 abuse/spam 표면이 늘어납니다 — 그래서 기본은 OFF 입니다. 켜는 즉시 변경되며 기존 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 버튼 라벨.

💡 Tip: 모바일/네이티브 RP 개발자 사용자에게 메시지를 그대로 노출하지 말고, application_name + remediation.action 을 조합해 자기 앱의 톤에 맞춰 다시 쓰는 것을 권장합니다. logi 가 돌려주는 한국어 카피는 안전한 fallback 입니다.

ℹ️ Note: QR 로그인 (모바일 → 데스크톱) 도 플래그를 존중 QR 로그인도 native authorize 와 동일하게 allow_anonymous_grants 를 따릅니다. 플래그가 ON 이면 익명 user 도 QR authorization 을 그대로 완료할 수 있습니다. 플래그가 OFF 면 QR 익명 시도가 차단되어 로그인/promotion 을 유도합니다.

데스크톱 브라우저가 띄운 QR 을 logi 앱이 스캔하고 POST /api/v1/oauth/qr/:id/approve 를 호출할 때, 플래그가 OFF 인 익명 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 목록을 주기적으로 점검하기를 권장합니다.

관련 문서

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