---
title: "sdk/android"
description: "Chrome Custom Tabs, PKCE S256 인증 코드 흐름, Keystore 기반 EncryptedSharedPreferences 토큰 저장소를 사용하는 Android용 Kotlin SDK."
locale: "ko"
---

> Documentation Index
> Fetch the relevant documentation index at: https://vonvon.id/ko/sdks/llms.txt
> Use this file to discover all available pages before exploring further.

# sdk/android

## 상태

Package 상태는 **구현 및 로컬 검증 완료**입니다. JVM 단위 테스트 suite가 통과했으며 PKCE, state 및 nonce 처리, guest capability, storage contracts를 다룹니다. EncryptedSharedPreferences, Chrome Custom Tabs, App Links 및 실제 IdP round-trip을 검증하려면 여전히 device 또는 emulator가 필요합니다. 이 페이지는 구현된 동작을 설명하며 production-ready 상태임을 주장하지 않습니다.

Registry 상태: UNPUBLISHED. 이 SDK는 저장소 소스 checkout에서만 설치하고 외부 package registry를 사용하지 마세요.

## 요구 사항

- Android API 26 이상(Android 8.0)
- Kotlin 1.9 이상 및 AndroidX

## 설치

앱 모듈의 build.gradle.kts에 의존성을 추가하세요:

```kotlin
// settings.gradle.kts
includeBuild("../vonvon/sdk/android")

// app/build.gradle.kts
dependencies {
implementation("dev.vonvon:vonvon-android:0.1.0-alpha.0")
}
```

## 매니페스트 설정

intent-filter가 있는 콜백 Activity를 등록하세요. 사용자 정의 스킴보다 App Links(autoVerify가 있는 HTTPS 스킴)를 권장합니다:

```xml
<!-- AndroidManifest.xml -->
<activity android:name=".AuthCallbackActivity" android:exported="true">
<intent-filter android:autoVerify="true">
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="https"
          android:host="yourapp.example.com"
          android:pathPrefix="/auth/callback" />
</intent-filter>
</activity>
```

## 빠른 시작

```kotlin
import dev.vonvon.sdk.Vonvon
import dev.vonvon.sdk.model.VonvonConfig

// 1. Initialize in Application.onCreate. offline_access is rejected until DPoP is implemented.
Vonvon.configure(
context = this,
config = VonvonConfig(
    issuer = "https://vonvon.id",
    clientId = "your_client_id",
    redirectUri = "https://yourapp.example.com/auth/callback",
    scopes = listOf("openid", "profile", "email"),
)
)

// 2. Sign in (opens Chrome Custom Tabs)
lifecycleScope.launch { Vonvon.signIn(requireContext()) }

// 3. Handle redirect in AuthCallbackActivity
val session = Vonvon.handleRedirect(intent.data.toString())

// 4. Read the current unexpired session. Expiry requires reauthorization.
val session = Vonvon.getSession()

// 5. Get the current unexpired access token.
val token = Vonvon.getAccessToken()

// 6. Clear local state and optionally open end_session.
Vonvon.signOut(context = this, openEndSession = true)
```

## 핵심 API

| 방법 | 서명 |
| --- | --- |
| `configure` | `fun configure(context: Context, config: VonvonConfig)` |
| `signIn` | `suspend fun signIn(context: Context, options: SignInOptions? = null)` |
| `handleRedirect` | `suspend fun handleRedirect(url: String): VonvonSession` |
| `getSession` | `suspend fun getSession(): VonvonSession?` |
| `getAccessToken` | `suspend fun getAccessToken(options: GetAccessTokenOptions? = null): String` |
| `signOut` | `suspend fun signOut(context: Context? = null, openEndSession: Boolean = false)` |
| `setTokenStorage` | `fun setTokenStorage(adapter: TokenStorageAdapter)` |

## 오류 타입

모든 SDK 오류는 봉인 클래스 VonvonException의 하위 타입입니다:

| 하위 클래스 | 트리거 |
| --- | --- |
| `NotConfigured` | configure()가 호출되지 않았습니다 |
| `UserCancelled` | 사용자가 완료하지 않고 Custom Tabs를 닫았습니다 |
| `StateMismatch` | OAuth state 불일치 — CSRF 가능성이 있습니다 |
| `TokenExchangeFailed` | token 엔드포인트에서 오류가 반환되었습니다 |
| `TokenValidationFailed` | ID token signature 또는 claims 검증 실패 |
| `NoSession` | 로그아웃 상태에서 세션 메서드가 호출되었습니다 |

## 보안

- 공개 클라이언트 — client secret이 저장되거나 전송되지 않습니다.
- PKCE S256만 사용합니다. 서버는 plain challenge 방식을 거부합니다.
- Android Keystore(AES-256-GCM) 기반의 EncryptedSharedPreferences가 저장된 토큰을 보호합니다.
- 요청마다 생성되는 무작위 OAuth state; CSRF를 방지하기 위해 리디렉션 시 검증됩니다.

## 알려진 제한 사항

- JWKS 기반 ID token 검증은 구현되어 로컬 단위 테스트를 통과했습니다. L4 지원 전에는 Android 기기 또는 emulator와 IdP 검증이 필요합니다.
- 사용자가 인증을 완료하지 않고 Custom Tabs를 닫는 경우를 감지하는 메커니즘이 없습니다.
- 단일 계정 전용 — 저장소 레이어는 고정 키를 사용합니다.

Source: https://vonvon.id/ko/sdks/android/index.mdx
