KMPAuth Documentation

repository·main·Indexed 19 days ago

https://github.com/mirzemehdi/kmpauth

A Kotlin Multiplatform authentication library for Compose Multiplatform apps. It provides a unified API for sign-in methods (Google, Apple, Facebook, etc.) across Android, iOS, Desktop, and Web, with built-in support for Firebase and Supabase backends or manual token verification. The library includes a session layer for managed backends, a credential layer for custom servers, and a UI helper module for brand-compliant buttons.

Tokens
18.6K
Snippets
54
Records
71
Agent score
68%

What's inside KMPAuth

  1. Configure Facebook Login Tracking (Limited vs Enabled)

    main

    You can control the privacy level and the type of token returned using loginTracking in both rememberFacebookSignInState and rememberFacebookAuthState:

    • FacebookLoginTracking.Limited (default): Uses privacy-friendly Limited Login. Returns an OIDC JWT + nonce. This does not require an iOS App Tracking Transparency (ATT) prompt. Firebase can exchange this via the OIDC OAuth provider.
    • FacebookLoginTracking.Enabled: Uses classic login. Returns a Graph-API access token in FacebookUser.accessToken. This counts as tracking on iOS and requires handling ATT.
    // Use Enabled if your backend specifically requires a Graph-API access token
    val facebookSignIn = rememberFacebookSignInState(
        loginTracking = FacebookLoginTracking.Enabled,
        onResult = { result -> val accessToken = result.getOrNull()?.accessToken },
    )
  2. Use Auth States instead of UI Containers for backend-agnostic authentication

    main

    KMPAuth 3.0 introduces a new pattern replacing the 2.x *UiContainer composables with rememberXxxAuthState (for session management) and rememberXxxSignInState (for credential retrieval).

    Key Benefits

    • Backend Agnostic: rememberXxxAuthState works with any registered backend (Firebase, Supabase, etc.) without changing your UI code.
    • Common Code Support: These states live in commonMain and are compatible with all targets, including Wasm.
    • Detailed Errors: Results are returned as Result<KMPAuthUser>, where a failure contains the specific reason instead of a null user.

    Mapping 2.x to 3.0

    2.x Container (Deprecated)3.0 ReplacementModulePurpose
    GoogleButtonUiContainerrememberGoogleSignInStatekmpauth-googleGet Google credentials
    GoogleButtonUiContainerFirebaserememberGoogleAuthStatekmpauth-googleManage Google session
    FacebookButtonUiContainerrememberFacebookSignInStatekmpauth-facebookGet Facebook credentials
    FacebookButtonUiContainerFirebaserememberFacebookAuthStatekmpauth-facebookManage Facebook session
    AppleButtonUiContainerrememberAppleAuthStatekmpauth-appleManage Apple session
    GithubButtonUiContainerrememberGithubAuthStatekmpauth-coreManage Github session
    OAuthContainerrememberOAuthState(provider = "...")kmpauth-coreManage OAuth session

    Implementation Example

    // 3.0 pattern
    val googleSignIn = rememberGoogleAuthState(onResult = { result: Result<KMPAuthUser> ->
        result.onSuccess {
            val name = it.displayName
            // Access raw FirebaseUser if needed via escape hatch:
            val nativeUser = it.raw as? dev.gitlive.firebase.auth.FirebaseUser
        }
    })
    
    GoogleSignInButton { googleSignIn.launch() }
    // 3.0
    val googleSignIn = rememberGoogleAuthState(onResult = { result: Result<KMPAuthUser> ->
        val name = result.getOrNull()?.displayName // uid, email, displayName, photoUrl, providerId
        val nativeUser = result.getOrNull()?.raw as? dev.gitlive.firebase.auth.FirebaseUser // escape hatch
    })
    GoogleSignInButton { googleSignIn.launch() }
  3. How OAuth flows behave on different backends and platforms

    main

    The behavior of OAuth flows depends on your chosen backend (Firebase or Supabase) and the target platform.

    Firebase Backend

    Prerequisite: Enable the provider in the Firebase console.

    • Android / iOS: Uses the Firebase SDK's native web-flow UI.
    • Desktop (JVM): Opens the system browser to a local page running Firebase's official JS SDK against your project's hosted auth handler. Supports all console-configured providers (including Apple).
    • Web (JS/wasm): Not yet implemented; will return a failed Result.

    Supabase Backend

    Prerequisite: Enable the provider in the Supabase dashboard. The provider parameter accepts GoTrue names like github, azure, gitlab, or discord.

    • Desktop (JVM): Works out of the box; supabase-kt handles the redirect via its own localhost callback server.
    • Android / iOS: Uses supabase-kt's standard deep-link setup.
    • Web (JS/wasm): Uses a full-page redirect; the session is restored after the page reloads.
  4. Use the recommended SignInState API instead of UiContainers

    main

    The *UiContainer pattern (e.g., GoogleButtonUiContainerFirebase) is deprecated in 3.0. The recommended approach is to use rememberXxxAuthState or rememberXxxSignInState composables. This gives you full control over the UI button while managing the authentication lifecycle.

    Key benefits:

    • SignInState.isInProgress can be used to drive loading spinners or disable buttons.
    • launch() is safe to call multiple times (subsequent calls are ignored if a flow is active).
    • Parameters are read at launch time, allowing for dynamic configuration (like toggling linkAccount) without recreating the state.

    Note on Callbacks: Session callbacks now return Result<KMPAuthUser> instead of Result<FirebaseUser?>.

    // 3.0 Recommended Pattern:
    val googleSignIn = rememberGoogleAuthState(
        linkAccount = false,
        onResult = onFirebaseResult,
    )
    
    // You can now use any custom button or UI component
    GoogleSignInButton(onClick = { googleSignIn.launch() })
  5. Extend KMPAuth with Custom Auth Backends

    main

    KMPAuth 3.0 features a pluggable architecture via com.mmk.kmpauth.core.auth.AuthProviderBackend. This allows you to use backend-agnostic models like AuthCredential and KMPAuthUser.

    • Automatic Registration: Firebase (FirebaseAuthBackend) registers itself automatically via ServiceLoader (JVM/Android) or eager loading (iOS/JS/wasm).
    • Manual Registration: To add a custom backend (like Supabase or a proprietary service), implement AuthProviderBackend and call KMPAuth.registerBackendProvider(yourBackend) at application startup.
    • Precedence: An explicit registration via registerBackendProvider always supersedes the auto-registered Firebase default. Use replace = true if you want to swap an existing registration.
  6. Compare Apple Sign-In flavors in `kmpauth-apple`

    main

    The kmpauth-apple module provides two distinct ways to implement Apple Sign-In depending on your backend architecture:

    MethodReturnsPlatformsBackend Requirement
    rememberAppleAuthStateResult<KMPAuthUser> sessionAll targets served by the backendFirebase or Supabase
    rememberAppleSignInStateResult<AppleUser> raw credentialiOS onlyNone (Manual verification)
  7. How the two layers of KMPAuth states work

    main

    KMPAuth provides two distinct layers of composable states for handling authentication in Compose, depending on whether you need a full session or just raw credentials.

    1. Credential only layer: Uses rememberXxxSignInState (e.g., rememberGoogleSignInState). This returns a Result<T> containing the raw provider user (e.g., GoogleUser). Use this when you do not have a backend and intend to handle the token/credential yourself.
    2. Session layer: Uses rememberXxxAuthState (e.g., rememberGoogleAuthState). This returns a Result<KMPAuthUser>. Use this when you have a registered backend (like Firebase or Supabase) that exchanges the credential for a managed session.

    Both layers return a SignInState which provides:

    • launch(): To start the authentication flow.
    • isInProgress: An observable boolean to drive loading UI and prevent double-taps.

    Key behaviors:

    • Parameters (like linkAccount) are read at launch time.
    • Results are non-null; failures (cancellation, misconfiguration, etc.) are returned as a failed Result with a specific reason.
    val googleSignIn = rememberGoogleAuthState(onResult = { result -> /* ... */ })
    Button(
        onClick = { googleSignIn.launch() }, 
        enabled = !googleSignIn.isInProgress
    ) {
        Text("Sign in with Google")
    }
  8. How the KMPAuth backend registry works

    main

    KMPAuth uses a registry to manage multiple authentication backends. Each backend is identified by a unique AuthProviderBackend.backendId (e.g., "firebase", "supabase", or a custom ID).

    Key behaviors:

    • Default Backend: The first backend registered during KMPAuth.initialize becomes the default backend. This default backend is used for non-keyed KMPAuth.* operations and for standard auth state hooks.
    • Multiple Backends: You can register multiple backends. Registering additional backends adds them to the registry without changing the existing default.
    • Switching Defaults: You can change which backend is considered the default at runtime using setDefaultBackendProvider(id).
    KMPAuth.initialize {
        firebase(apiKey = ..., projectId = ..., applicationId = ...) // self-registers, becomes default
        supabase(url = ..., apiKey = ...)                            // registered under "supabase"
        // defaultBackendProvider("supabase")  // optional: make Supabase the default
    }
    
    KMPAuth.getBackendProvider()             // returns the default backend
    KMPAuth.requireBackendProvider("supabase") // retrieves a specific backend by ID
    KMPAuth.setDefaultBackendProvider("supabase") // switches the default backend later
  9. Link accounts to upgrade guest sessions

    main

    To upgrade an anonymous (guest) user to a permanent account while preserving their uid and data, use the account linking feature.

    When calling rememberXxxAuthState flows or KMPAuth.signIn(), set the parameter linkAccount = true (or linkWithCurrentUser = true). This ensures the new credential is attached to the existing session instead of creating a new one.

  10. Setup Facebook Sign-In on iOS

    main

    1. Install SDK and Configure Info.plist

    Add the Facebook SDK Swift package to your project, then add these keys to your Info.plist:

    <key>CFBundleURLTypes</key>
    <array>
      <dict>
        <key>CFBundleURLSchemes</key>
        <array>
          <string>fbFACEBOOK_APP_ID</string> <!-- Your Facebook App ID with 'fb' prefix -->
        </array>
      </dict>
    </array>
    
    <key>FacebookAppID</key>
    <string>FACEBOOK_APP_ID</string>
    
    <key>FacebookClientToken</key>
    <string>YOUR_FACEBOOK_CLIENT_TOKEN</string>
    
    <key>FacebookDisplayName</key>
    <string>YourAppDisplayName</string>
    
    <key>LSApplicationQueriesSchemes</key>
    <array>
      <string>fbapi</string>
      <string>fb-messenger-api</string>
      <string>fbauth2</string>
      <string>fbshareextension</string>
    </array>

    2. Initialize SDK in AppDelegate

    In your Swift code, initialize the Facebook SDK within the application lifecycle methods:

    func application(_ application: UIApplication,
                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
        FirebaseApp.configure()
        FBSDKCoreKit.ApplicationDelegate.shared.application(
            application,
            didFinishLaunchingWithOptions: launchOptions
        )
        return true
    }
    
    func application(
        _ app: UIApplication,
        open url: URL,
        options: [UIApplication.OpenURLOptionsKey : Any] = [:]
    ) -> Bool {
        return FBSDKCoreKit.ApplicationDelegate.shared.application(
            app,
            open: url,
            options: options
        )
    }
    func application(_ application: UIApplication,
                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
        FirebaseApp.configure()
        FBSDKCoreKit.ApplicationDelegate.shared.application(
            application,
            didFinishLaunchingWithOptions: launchOptions
        )
        return true
    }
  11. Implement Browser OAuth with Supabase across platforms

    main

    Browser-based OAuth flows behave differently depending on the target platform:

    • Desktop (JVM): Works out of the box. supabase-kt opens the system browser and uses a local callback server. You must allow-list http://localhost:<port> in your Supabase dashboard. The port can be configured via supabase-kt's httpCallbackConfig.
    • Android / iOS: Requires standard supabase-kt deep-link setup. You must set scheme/host on the Supabase client, register the scheme in AndroidManifest.xml or Info.plist, forward the link using handleDeeplinks, and allow-list the redirect URL in the Supabase dashboard.
    • Web (JS/wasm): The page redirects to the provider, causing the app to unload. No Result callback will fire. The session is restored by supabase-kt automatically when the page reloads after the redirect.
    // Example Browser OAuth call
    KMPAuth.signIn(AuthCredential.OAuthWebFlow("github.com"))
  12. Initialize KMPAuth at application start

    main

    Use KMPAuth.initialize once at the start of your application to configure providers and backends.

    • Google: Requires serverId.
    • Firebase: On Android/iOS, the backend registers itself automatically if the Firebase SDK can find the bundled config files. On Desktop/Web, you must provide the apiKey, projectId, and applicationId manually.
    • Supabase: Requires url and apiKey (publishable key).
    • Logging: You can optionally provide a logger block.
    KMPAuth.initialize {
        logger { println("KMPAuthLog: $it") }          // optional
        google(serverId = WebClientId)                  // kmpauth-google
        
        // Firebase (Required for Desktop/Web; auto-config on Android/iOS)
        firebase(apiKey = "...", projectId = "...", applicationId = "...")
        
        // OR Supabase
        // supabase(url = projectUrl, apiKey = publishableKey)
    }