Packages/kandi-login/Android (Compose)

kandi-login / Android (Compose)

Coming Soon

Native Android authentication using Chrome Custom Tabs for OAuth and EncryptedSharedPreferences for secure token storage. Built for Jetpack Compose with Material 3.

Download Example Project

Quick Start

1

Clone the example project

git clone https://github.com/KandiForge/kandi-packages.git
cd kandi-packages/examples/android
2

Open in Android Studio

# Open kandi-packages/examples/android in Android Studio
3

Build and run on an emulator or device

4

Tap "Login" — Chrome Custom Tabs handles the OAuth flow, then returns to the app

Auth Configuration

AuthConfig.kt
// AuthConfig.kt
import com.kandiforge.kandilogin.KandiAuthConfig

val authConfig = KandiAuthConfig(
    authServerUrl = "https://api.packages.kandiforge.com",
    authBasePath = "/api/auth",
    providers = listOf(Provider.GOOGLE, Provider.GITHUB),
    // Tokens stored in EncryptedSharedPreferences
    secureStorage = true,
    // Deep-link scheme for OAuth callback
    deepLinkScheme = "myapp"
)

Switch to Your Server

The example app points to the KandiForge reference server by default. Once you have your own server running:

// Change authServerUrl to your own server: authServerUrl = "https://your-api.example.com"

Run the conformance validator against your server to confirm it implements all required endpoints.

Test Personas for Automated Testing

Use the test persona system to authenticate in Espresso, and Compose UI Test tests without launching a browser OAuth flow.

androidTest/AuthTestSetup.ktNo browser needed
// androidTest/AuthTestSetup.kt
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody

class AuthTestSetup {
    private val client = OkHttpClient()
    private val baseUrl = "https://your-api.example.com/api/auth"

    fun authenticateAsPersona(personaId: String): AuthTokens {
        // Seed personas (idempotent)
        client.newCall(
            Request.Builder().url("$baseUrl/test/seed").post("".toRequestBody()).build()
        ).execute()

        // Get real JWT tokens — no Custom Tabs needed
        val body = """{"personaId": "$personaId"}""".toRequestBody()
        val response = client.newCall(
            Request.Builder()
                .url("$baseUrl/test/login-as")
                .post(body)
                .addHeader("Content-Type", "application/json")
                .build()
        ).execute()

        return parseTokens(response.body!!.string())
    }
}

Default personas: admin-alex, designer-dana, viewer-val, new-user-naya. See all personas