
Drop-in authentication screens, customizable buttons, and headless manager for Supabase Auth; three-level API enables Google, Apple, magic-link, and anonymous sign-in with reactive session state.
A Kotlin Multiplatform authentication library for Android & iOS — built on Supabase Auth.
One dependency. Four auth methods. Zero boilerplate.
Flow<AuthState> for observing session changesAdd the dependency in your module's build.gradle.kts:
// KMP (commonMain)
implementation("io.github.xemb0:auth:1.0.0")
// Android-only
implementation("io.github.xemb0:auth-android:1.0.0")That's it — resolves from Maven Central, no extra repositories needed.
AuthConfig.initialize(
supabaseUrl = "https://your-project.supabase.co",
supabaseAnonKey = "your-anon-key",
googleWebClientId = "your-google-client-id",
deepLinkScheme = "com.your.app",
appName = "MyApp",
appTagline = "Welcome back!",
enabledMethods = setOf(
AuthMethod.GOOGLE,
AuthMethod.APPLE,
AuthMethod.EMAIL, // Magic Link
AuthMethod.ANONYMOUS // Optional guest mode
)
)AuthScreen(
supabaseClient = supabaseClient,
googleSignInService = googleSignInService,
authManager = authManager,
onAuthenticated = { /* navigate to home */ },
onEmailSignIn = { /* navigate to magic link flow */ }
)That's it. You get a fully styled login screen with all enabled providers.
Drop-in screens that handle the entire auth flow.
// Auth gate — auto-routes based on session state
AuthGate(
authManager = authManager,
authContent = { AuthScreen(...) },
authenticatedContent = { HomeScreen() }
)Screens included:
| Screen | Description |
|---|---|
AuthScreen |
Full login screen with all configured providers |
AuthGate |
Session-aware gate (loading → login or content) |
MagicLinkEmailScreen |
Email input for magic link / OTP |
MagicLinkOtpScreen |
"Check your email" confirmation screen |
Use standalone buttons anywhere in your UI.
GoogleSignInButton(
supabaseClient = supabaseClient,
googleSignInService = googleSignInService,
onResult = { result ->
when (result) {
is AuthResult.Success -> { /* logged in */ }
is AuthResult.Cancelled -> { /* user cancelled */ }
is AuthResult.Error -> { /* show error */ }
}
}
)
AppleSignInButton(
supabaseClient = supabaseClient,
onResult = { /* handle result */ }
)
EmailSignInButton(
onClick = { /* navigate to magic link flow */ }
)
AnonymousSignInButton(
authManager = authManager,
onResult = { /* handle result */ }
)All buttons are individually customizable (text, shape, height, colors).
Use AuthManager directly — no UI, full control.
val authManager: AuthManager = SupabaseAuthManager(supabaseClient)
// Anonymous sign-in
val userId = authManager.signInAnonymously()
// Magic link
authManager.sendMagicLink("user@example.com")
// Check session
val isLoggedIn = authManager.isAuthenticated()
val user: AuthUser? = authManager.getCurrentUser()
val token: String? = authManager.getAccessToken()
// Observe auth state reactively
authManager.observeAuthState().collect { state ->
when (state) {
is AuthState.Loading -> { /* restoring session */ }
is AuthState.Authenticated -> { /* user.id = state.userId */ }
is AuthState.Unauthenticated -> { /* show login */ }
}
}
// Sign out
authManager.signOut()AuthConfig.initialize(
// ...
appName = "MyApp",
appTagline = "Your awesome tagline",
appLogo = { Image(painterResource(R.drawable.logo), "Logo") }, // or null for text
appNameColor = Color(0xFF6C63FF),
primaryColor = Color(0xFF6C63FF), // null = use MaterialTheme
accentColor = Color(0xFFFF6584),
)// Only Google + Email (Magic Link)
enabledMethods = setOf(AuthMethod.GOOGLE, AuthMethod.EMAIL)
// Only Apple
enabledMethods = setOf(AuthMethod.APPLE)
// All four
enabledMethods = setOf(AuthMethod.GOOGLE, AuthMethod.APPLE, AuthMethod.EMAIL, AuthMethod.ANONYMOUS)AuthConfig.initialize(
// ...
strings = object : AuthStrings() {
override val welcome = "Bienvenido"
override val continueWithGoogle = "Continuar con Google"
override val signInWithApple = "Iniciar sesion con Apple"
override val continueWithEmail = "Continuar con Email"
override val continueAsGuest = "Continuar como invitado"
override val orDivider = "o"
override val termsOfService = "Al continuar, aceptas nuestros Terminos de Servicio"
}
)AuthScreen(
// ...
headerContent = {
Text("Special offer: 50% off!", style = MaterialTheme.typography.bodyLarge)
},
footerContent = {
TextButton(onClick = { /* privacy policy */ }) {
Text("Privacy Policy")
}
}
)val callbacks = object : AuthCallbacks {
override suspend fun onLoginSuccess(userId: String, method: String) {
analytics.log("login_success", mapOf("method" to method))
}
override suspend fun onLoginError(method: String, error: String) {
analytics.log("login_error", mapOf("method" to method, "error" to error))
}
override suspend fun onSignOut() {
analytics.log("sign_out")
}
}The library ships with Koin modules for dependency injection:
// In your app's Koin setup
startKoin {
modules(
authModule, // provides AuthManager
authPlatformModule, // provides GoogleSignInService (platform-specific)
// your app modules...
)
}Add to your AndroidManifest.xml:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="com.your.app" android:host="login-callback" />
</intent-filter>Handle in your Activity:
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
handleAuthDeepLink(intent)
}Add URL scheme in Info.plist and handle in AppDelegate:
func application(_ app: UIApplication, open url: URL, options: ...) -> Bool {
handleAuthDeepLink(url: url)
return true
}| Version | |
|---|---|
| Kotlin | 2.3+ |
| Compose Multiplatform | 1.10+ |
| Supabase BOM | 3.3+ |
| Android minSdk | 26 |
| iOS | arm64, simulatorArm64 |
MIT License
Copyright (c) 2025 Xemb0
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
A Kotlin Multiplatform authentication library for Android & iOS — built on Supabase Auth.
One dependency. Four auth methods. Zero boilerplate.
Flow<AuthState> for observing session changesAdd the dependency in your module's build.gradle.kts:
// KMP (commonMain)
implementation("io.github.xemb0:auth:1.0.0")
// Android-only
implementation("io.github.xemb0:auth-android:1.0.0")That's it — resolves from Maven Central, no extra repositories needed.
AuthConfig.initialize(
supabaseUrl = "https://your-project.supabase.co",
supabaseAnonKey = "your-anon-key",
googleWebClientId = "your-google-client-id",
deepLinkScheme = "com.your.app",
appName = "MyApp",
appTagline = "Welcome back!",
enabledMethods = setOf(
AuthMethod.GOOGLE,
AuthMethod.APPLE,
AuthMethod.EMAIL, // Magic Link
AuthMethod.ANONYMOUS // Optional guest mode
)
)AuthScreen(
supabaseClient = supabaseClient,
googleSignInService = googleSignInService,
authManager = authManager,
onAuthenticated = { /* navigate to home */ },
onEmailSignIn = { /* navigate to magic link flow */ }
)That's it. You get a fully styled login screen with all enabled providers.
Drop-in screens that handle the entire auth flow.
// Auth gate — auto-routes based on session state
AuthGate(
authManager = authManager,
authContent = { AuthScreen(...) },
authenticatedContent = { HomeScreen() }
)Screens included:
| Screen | Description |
|---|---|
AuthScreen |
Full login screen with all configured providers |
AuthGate |
Session-aware gate (loading → login or content) |
MagicLinkEmailScreen |
Email input for magic link / OTP |
MagicLinkOtpScreen |
"Check your email" confirmation screen |
Use standalone buttons anywhere in your UI.
GoogleSignInButton(
supabaseClient = supabaseClient,
googleSignInService = googleSignInService,
onResult = { result ->
when (result) {
is AuthResult.Success -> { /* logged in */ }
is AuthResult.Cancelled -> { /* user cancelled */ }
is AuthResult.Error -> { /* show error */ }
}
}
)
AppleSignInButton(
supabaseClient = supabaseClient,
onResult = { /* handle result */ }
)
EmailSignInButton(
onClick = { /* navigate to magic link flow */ }
)
AnonymousSignInButton(
authManager = authManager,
onResult = { /* handle result */ }
)All buttons are individually customizable (text, shape, height, colors).
Use AuthManager directly — no UI, full control.
val authManager: AuthManager = SupabaseAuthManager(supabaseClient)
// Anonymous sign-in
val userId = authManager.signInAnonymously()
// Magic link
authManager.sendMagicLink("user@example.com")
// Check session
val isLoggedIn = authManager.isAuthenticated()
val user: AuthUser? = authManager.getCurrentUser()
val token: String? = authManager.getAccessToken()
// Observe auth state reactively
authManager.observeAuthState().collect { state ->
when (state) {
is AuthState.Loading -> { /* restoring session */ }
is AuthState.Authenticated -> { /* user.id = state.userId */ }
is AuthState.Unauthenticated -> { /* show login */ }
}
}
// Sign out
authManager.signOut()AuthConfig.initialize(
// ...
appName = "MyApp",
appTagline = "Your awesome tagline",
appLogo = { Image(painterResource(R.drawable.logo), "Logo") }, // or null for text
appNameColor = Color(0xFF6C63FF),
primaryColor = Color(0xFF6C63FF), // null = use MaterialTheme
accentColor = Color(0xFFFF6584),
)// Only Google + Email (Magic Link)
enabledMethods = setOf(AuthMethod.GOOGLE, AuthMethod.EMAIL)
// Only Apple
enabledMethods = setOf(AuthMethod.APPLE)
// All four
enabledMethods = setOf(AuthMethod.GOOGLE, AuthMethod.APPLE, AuthMethod.EMAIL, AuthMethod.ANONYMOUS)AuthConfig.initialize(
// ...
strings = object : AuthStrings() {
override val welcome = "Bienvenido"
override val continueWithGoogle = "Continuar con Google"
override val signInWithApple = "Iniciar sesion con Apple"
override val continueWithEmail = "Continuar con Email"
override val continueAsGuest = "Continuar como invitado"
override val orDivider = "o"
override val termsOfService = "Al continuar, aceptas nuestros Terminos de Servicio"
}
)AuthScreen(
// ...
headerContent = {
Text("Special offer: 50% off!", style = MaterialTheme.typography.bodyLarge)
},
footerContent = {
TextButton(onClick = { /* privacy policy */ }) {
Text("Privacy Policy")
}
}
)val callbacks = object : AuthCallbacks {
override suspend fun onLoginSuccess(userId: String, method: String) {
analytics.log("login_success", mapOf("method" to method))
}
override suspend fun onLoginError(method: String, error: String) {
analytics.log("login_error", mapOf("method" to method, "error" to error))
}
override suspend fun onSignOut() {
analytics.log("sign_out")
}
}The library ships with Koin modules for dependency injection:
// In your app's Koin setup
startKoin {
modules(
authModule, // provides AuthManager
authPlatformModule, // provides GoogleSignInService (platform-specific)
// your app modules...
)
}Add to your AndroidManifest.xml:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="com.your.app" android:host="login-callback" />
</intent-filter>Handle in your Activity:
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
handleAuthDeepLink(intent)
}Add URL scheme in Info.plist and handle in AppDelegate:
func application(_ app: UIApplication, open url: URL, options: ...) -> Bool {
handleAuthDeepLink(url: url)
return true
}| Version | |
|---|---|
| Kotlin | 2.3+ |
| Compose Multiplatform | 1.10+ |
| Supabase BOM | 3.3+ |
| Android minSdk | 26 |
| iOS | arm64, simulatorArm64 |
MIT License
Copyright (c) 2025 Xemb0
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.