Skip to content

Android (Kotlin) 통합

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

📍 Jump-to Index

  • L32-L50: ## 자격증명 보관
  • L51-L70: ## Claims 모델
  • L71-L90: ## 토큰 검증 후 user 매핑
  • L91-L104: ## linked_subs 처리
  • L105-L115: ## Anonymous → Identified 전환 감지
  • L116-L119: ## OAuth 흐름
  • L120-L129: ## 통합 동작 검증
  • L130-L133: ## 트러블슈팅: AlreadyLinkedError
  • L134-L141: ## 관련 문서

💡 Tip: TODO (IA — follow-up) 이 문서와 Android 통합 의 정보 아키텍처가 겹칩니다. 편집 결정 후 둘 중 하나를 canonical 로 정리할 예정. 현재는 이 문서를 Account Merge / canonical_sub addendum 으로 사용하세요. OAuth/저장소 기본 패턴은 Android 통합 을 따르세요.

Android (Kotlin) 통합

이 문서는 Android 네이티브 앱(Jetpack Compose / View XML 무관) 에서 logi 를 RP 로 통합하는 표준 패턴을 다룹니다. iOS Swift 가이드 (Swift) 의 Android 미러이며, OAuth 흐름과 자격증명 보관에서 같은 약속을 제공합니다.

전반적 아키텍처 권장 사항은 Android 통합 페이지에 더 자세히 있고, 이 문서는 canonical_sub / linked_subs 처리자격증명 분리 보관 패턴에 중점을 둡니다.

자격증명 보관

logi 가 발급하는 두 가지 클라이언트-side 비밀:

비밀용도만료
refresh_tokenaccess_token 재발급30일 (rotation)
device_secret익명 user 의 1차 PoP, device credential무기한 (디바이스 lifecycle)

저장 패턴은 Android 통합 — Refresh Token 저장DataStore + Tink 패턴을 따르되 두 비밀을 별도 DataStore 키 (alias) 로 분리합니다 — 한쪽을 revoke/wipe 할 때 다른 쪽에 영향이 없도록.

⚠️ Warning: EncryptedSharedPreferences 금지 androidx.security:security-cryptoEncryptedSharedPreferences 는 1.1.0 부터 deprecated. 신규 코드에서는 DataStore + Tink 패턴만 사용하세요. device_secret 도 절대 plain SharedPreferences 에 저장 금지 — 백업에 평문 노출됩니다.

Claims 모델

kotlin
@Serializable
data class LogiClaims(
    val sub: String,
    @SerialName("canonical_sub") val canonicalSub: String? = null,
    @SerialName("is_canonical") val isCanonical: Boolean? = null,
    @SerialName("linked_subs") val linkedSubs: List<String>? = null,
    @SerialName("previously_anonymous") val previouslyAnonymous: Boolean? = null,
    val email: String? = null,
    @SerialName("email_verified") val emailVerified: Boolean? = null,
    val anonymous: Boolean? = null
) {
    /** 외래키 / 로컬 캐시 lookup 시 항상 이 값을 쓴다. */
    val effectiveSub: String
        get() = canonicalSub ?: sub
}

토큰 검증 후 user 매핑

kotlin
class LogiSessionHandler(private val localStore: LocalStore) {
    fun handle(claims: LogiClaims) {
        val canonical = claims.effectiveSub
        val currentLocal = localStore.userId
        if (currentLocal != null && currentLocal != canonical) {
            // 통합 발생 — 로컬 캐시를 canonical 로 갱신.
            localStore.migrateUser(from = currentLocal, to = canonical)
        }
        localStore.userId = canonical

        if (claims.previouslyAnonymous == true) {
            localStore.previouslyAnonymous = true
        }
    }
}

linked_subs 처리

흡수한 쪽 (isCanonical == true) 이 갖고 있던 옛 sub 들이 있다면, 로컬 캐시의 데이터를 canonical sub view 로 끌어옵니다:

kotlin
if (claims.isCanonical == true) {
    claims.linkedSubs?.forEach { absorbed ->
        localStore.reassignData(from = absorbed, to = claims.sub)
    }
}

reassignData 는 idempotent 하게 작성합니다 — 이미 옮겨진 sub 는 skip.

Anonymous → Identified 전환 감지

kotlin
if (claims.anonymous == false && localStore.wasAnonymous) {
    // promotion 발생: 익명 시절 device-local 데이터를
    // promoted user 의 cloud 계정으로 sync.
    syncEngine.runPromotionUpload()
    localStore.wasAnonymous = false
}

OAuth 흐름

Authorization Code + PKCE 흐름은 Android 페이지의 표준 패턴을 그대로 사용합니다. 이 문서가 다루는 것은 그 위에 얹히는 canonical 인식 layer 입니다 — OAuth 자체의 redirect/state/code_verifier 처리는 기존 가이드 참조.

통합 동작 검증

production flip 전 다음을 staging 에서 확인:

  • [ ] 두 SSO 가 같은 디바이스에서 발생했을 때 (T1) 다음 토큰의 canonical_sub 가 적절히 채워짐.
  • [ ] 같은 이메일의 SSO 로 처음 가입했을 때 (T2) linked_subs 가 즉시 들어옴.
  • [ ] T3 (/me/merge via WebView) 실행 후 다음 토큰 회전에서 canonical 이 바뀜.
  • [ ] 익명-우선 가입 + SSO promotion 후 previously_anonymous == true.
  • [ ] 흡수된 쪽의 refresh token 이 즉시 invalid (refresh_token_revoked 응답).

트러블슈팅: AlreadyLinkedError

같은 사람이 web/iOS/Android 에서 각각 다른 client_id 로 로그인하면 pairwise sub 이 플랫폼마다 다릅니다. RP 가 sub 으로 사용자를 키잉하면 두 번째 플랫폼 로그인 때 "이미 다른 계정과 연결" 로 깨집니다 — 사람 단위 식별은 위 effectiveSub(= canonicalSub) 로 하세요. 기존 자체 계정에 logi 를 붙이는 경우, 무로그인 email-bootstrap(takeover 위험) 대신 로그인된 세션 위 명시적 link 패턴을 따르세요: Rails 가이드: account linking.

관련 문서

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