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.
Quick Start
1
Clone the example project
git clone https://github.com/KandiForge/kandi-packages.git
cd kandi-packages/examples/android2
Open in Android Studio
# Open kandi-packages/examples/android in Android Studio3
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