> ## Documentation Index
> Fetch the complete documentation index at: https://docs-staging.auth0-mintlify.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Add Login to Your Kotlin Multiplatform Application using the Auth0 Kotlin Multiplatform SDK

> Add authentication to a Kotlin Multiplatform (Android + iOS) app using the Auth0 Kotlin Multiplatform SDK.

export const HowToSchema = () => <script type="application/ld+json">
    {'{"@context":"https://schema.org","@type":"HowTo"}'}
  </script>;

<HowToSchema />

<Accordion title="Use AI to integrate Auth0" icon="microchip-ai" iconType="solid" defaultOpen>
  If you use an AI coding assistant like Claude Code, Cursor, or GitHub Copilot, you can add Auth0 authentication automatically in minutes using [agent skills](https://agentskills.io/home).

  **Install:**

  ```bash theme={null}
  npx skills add auth0/agent-skills --skill auth0
  ```

  **Then ask your AI assistant:**

  ```text theme={null}
  Add Auth0 authentication to my Kotlin Multiplatform app.
  ```

  Your AI assistant automatically creates your Auth0 application, fetches credentials, adds the Auth0 Kotlin Multiplatform SDK dependency, configures the Android manifest placeholders and iOS URL scheme, and implements login/logout flows. [Read the full agent skills documentation](/docs/quickstart/agent-skills).
</Accordion>

<Note>
  Use this quickstart with Kotlin Multiplatform 2.0+, with Android SDK 24+, (Android 7.0), and iOS 14+. The Auth0 Kotlin Multiplatform SDK is currently in 1.0.0-beta.0. Pin this version explicitly, as the API may change before the stable release. You need [Android Studio](https://developer.android.com/studio) (Ladybug or newer) and, for the iOS target, [Xcode 15+](https://developer.apple.com/xcode/) on macOS.
</Note>

## Get Started

Use this quickstart to configure your Kotlin Multiplatform app for end users to log in and out through Auth0 [Universal Login](/docs/authenticate/login/auth0-universal-login), persist tokens securely, and display user profiles — all from shared Kotlin code running on both Android and iOS.

<Steps>
  <Step title="Create a new Kotlin Multiplatform project" stepNumber={1}>
    If you already have a Kotlin Multiplatform project, skip to the next step.

    Create a new Kotlin Multiplatform project with a shared Compose UI using the [JetBrains Kotlin Multiplatform wizard](https://kmp.jetbrains.com/) (select **Android** and **iOS**, and share the UI with **Compose Multiplatform**), or the **Kotlin Multiplatform** plugin in Android Studio.

    This produces the standard layout referenced throughout this guide:

    ```text theme={null}
    your-app/
    ├── composeApp/
    │   ├── build.gradle.kts
    │   └── src/
    │       ├── commonMain/kotlin/    ← shared code (Auth0 client, view model, UI)
    │       ├── androidMain/          ← Android entry point + AndroidManifest.xml
    │       └── iosMain/kotlin/       ← iOS entry point
    ├── iosApp/                       ← Xcode project (Info.plist lives here)
    └── settings.gradle.kts
    ```

    <Info>
      This guide uses `com.example.app` as the application ID / bundle identifier. Replace this placeholder with your own since it becomes part of your Auth0 callback URLs.
    </Info>
  </Step>

  <Step title="Add the Auth0 SDK via Gradle" stepNumber={2}>
    Add the Auth0 Kotlin Multiplatform SDK to the `commonMain` source set of your shared module. The library is published to Maven Central, so no extra repository configuration is required.

    **Update `composeApp/build.gradle.kts`:**

    ```kotlin composeApp/build.gradle.kts theme={null}
    kotlin {
        sourceSets {
            commonMain.dependencies {
                // Umbrella module — aggregates web auth, authentication, and credentials
                implementation("com.auth0.kmp:auth0:1.0.0-beta.0")
            }
        }
    }
    ```

    <Tip>
      The umbrella `auth0` artifact pulls in everything you need. For a smaller footprint, you can depend on the following individual modules instead: `auth0-core`, `auth0-webauth`, `auth0-authentication`, `auth0-credentials`.
    </Tip>
  </Step>

  <Step title="Setup your Auth0 App" stepNumber={3}>
    Create a Native application in Auth0 and register the platform callback and logout URLs for both Android and iOS.

    1. Navigate to the [Auth0 Dashboard](https://manage.auth0.com/dashboard/).
    2. Select **Applications** > **Applications** > **Create Application**.
    3. In the popup, enter a name for your app, select **Native** as the app type, and choose **Create**.
    4. Switch to the Settings tab on the Application Details page and copy the Domain and Client ID. You need to add them to your code in a later step.

    Still on the Settings tab, configure the following URLs. The callback format is scheme-specific per platform, so register one entry for Android and one for iOS:

    **Allowed Callback URLs:**

    ```
    com.example.app://{yourDomain}/android/com.example.app/callback, com.example.app://{yourDomain}/ios/com.example.app/callback
    ```

    **Allowed Logout URLs:**

    ```
    com.example.app://{yourDomain}/android/com.example.app/callback, com.example.app://{yourDomain}/ios/com.example.app/callback
    ```

    Replace `{yourDomain}` with your actual Auth0 domain (e.g., `dev-abc123.us.auth0.com`) and `com.example.app` with your application ID / bundle identifier.

    <Info>
      Allowed Callback URLs ensure end users are safely returned to your application after authentication. Without a matching URL, the login process will fail. Allowed Logout URLs ensure users are redirected back to your app after signing out.

      The callback format embeds your package/bundle identifier: `SCHEME://YOUR_DOMAIN/android/APPLICATION_ID/callback` for Android and `SCHEME://YOUR_DOMAIN/ios/BUNDLE_ID/callback` for iOS. By default the scheme equals your application ID / bundle identifier.
    </Info>

    <Note>
      **Important**: Ensure the package/bundle name in your callback URLs matches your `applicationId` (Android) and bundle identifier (iOS) exactly. If authentication fails, verify these values are identical.
    </Note>
  </Step>

  <Step title="Register the callback scheme on each platform" stepNumber={4}>
    The SDK ships a `RedirectActivity` (Android, merged automatically) and uses `ASWebAuthenticationSession` (iOS) to catch the callback. You only need to declare the URL scheme on each platform.

    **Android**: Add the manifest placeholders to your Android build file. The SDK's `RedirectActivity` reads these values:

    ```kotlin composeApp/build.gradle.kts theme={null}
    android {
        defaultConfig {
            
            manifestPlaceholders["auth0Scheme"] = "{yourScheme}" // default: your application ID
            manifestPlaceholders["auth0Domain"] = "{yourDomain}"
        }
    }
    ```

    Also ensure your `AndroidManifest.xml` requests the Internet permission:

    ```xml composeApp/src/androidMain/AndroidManifest.xml theme={null}
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
        <uses-permission android:name="android.permission.INTERNET" />
    </manifest>
    ```

    **iOS**: Register the URL scheme in `iosApp/iosApp/Info.plist` (or via Xcode → target → **Info** → **URL Types**):

    ```xml iosApp/iosApp/Info.plist theme={null}
    <key>CFBundleURLTypes</key>
    <array>
        <dict>
            <key>CFBundleTypeRole</key>
            <string>Editor</string>
            <key>CFBundleURLSchemes</key>
            <array>
                <!-- Must match your bundle identifier -->
                <string>com.example.app</string>
            </array>
        </dict>
    </array>
    ```
  </Step>

  <Step title="Initialize the Auth0 SDK" stepNumber={5}>
    In your shared `commonMain` source set, create the `Auth0` client once and reuse it. It holds the transport shared by web auth, the [Authentication API](/docs/api/authentication), and the credentials manager.

    **Create `composeApp/src/commonMain/kotlin/Auth0Config.kt`:**

    ```kotlin composeApp/src/commonMain/kotlin/Auth0Config.kt theme={null}
    import com.auth0.kmp.Auth0
    import com.auth0.kmp.core.Auth0Account

    // Shared code — one account works for Android and iOS
    val account = Auth0Account(
        clientId = "YOUR_AUTH0_CLIENT_ID", // From Application Settings → Client ID
        domain = "{yourDomain}",           // From Application Settings → Domain
    )

    val auth0 = Auth0(account)
    ```

    <Info>
      For production, avoid hard-coding credentials in source. The [sample app](https://github.com/auth0/auth0-kmp/tree/main/sample-app) reads `auth0.domain` and `auth0.clientId` from `local.properties` and exposes them via generated config. The Domain and Client ID both come from Application Settings in the Auth0 Dashboard. The domain must not include the `https://` scheme.
    </Info>
  </Step>

  <Step title="Implement Login and Logout" stepNumber={6}>
    Every Auth0 Kotlin Multiplatform method is a coroutine `suspend` function that returns a `Result<Success, Error>` — no exceptions are thrown for domain errors. Wrap the calls in a `ViewModel` so your Compose UI can observe the state.

    **Create `composeApp/src/commonMain/kotlin/AuthViewModel.kt`:**

    ```kotlin composeApp/src/commonMain/kotlin/AuthViewModel.kt theme={null}
    import androidx.lifecycle.ViewModel
    import androidx.lifecycle.viewModelScope
    import com.auth0.kmp.core.result.Result
    import kotlinx.coroutines.flow.MutableStateFlow
    import kotlinx.coroutines.flow.StateFlow
    import kotlinx.coroutines.flow.asStateFlow
    import kotlinx.coroutines.launch

    sealed interface AuthState {
        data object LoggedOut : AuthState
        data object Loading : AuthState
        data class LoggedIn(val accessToken: String) : AuthState
        data class Error(val message: String) : AuthState
    }

    class AuthViewModel(val auth0: Auth0) : ViewModel() {
        
        // Persists and auto-renews tokens (Keystore/DataStore on Android, Keychain on iOS)
        private val credentialsManager = auth0.credentials()

        private val _state = MutableStateFlow<AuthState>(AuthState.LoggedOut)
        val state: StateFlow<AuthState> = _state.asStateFlow()

        fun login() {
            viewModelScope.launch {
                _state.value = AuthState.Loading
                // Opens Universal Login in the system browser
                when (val result = auth0.webAuth.login()) {
                    is Result.Success -> {
                        credentialsManager.saveCredentials(result.data)
                        _state.value = AuthState.LoggedIn(result.data.accessToken)
                    }
                    is Result.Failure -> {
                        _state.value = AuthState.Error(result.error.toString())
                    }
                }
            }
        }

        fun logout() {
            viewModelScope.launch {
                // Clears the browser session, then the locally stored credentials
                when (val result = auth0.webAuth.logout()) {
                    is Result.Success -> {
                        credentialsManager.clearCredentials()
                        _state.value = AuthState.LoggedOut
                    }
                    is Result.Failure -> {
                        _state.value = AuthState.Error(result.error.toString())
                    }
                }
            }
        }
    }
    ```

    Wire the view model into a Compose screen shared across both platforms:

    ```kotlin composeApp/src/commonMain/kotlin/App.kt theme={null}
    import androidx.compose.material3.Button
    import androidx.compose.material3.CircularProgressIndicator
    import androidx.compose.material3.MaterialTheme
    import androidx.compose.material3.Text
    import androidx.compose.runtime.Composable
    import androidx.compose.runtime.getValue
    import androidx.lifecycle.compose.collectAsStateWithLifecycle
    import androidx.lifecycle.viewmodel.compose.viewModel

    @Composable
    fun App(viewModel: AuthViewModel = viewModel { AuthViewModel() }) {
        MaterialTheme {
            val state by viewModel.state.collectAsStateWithLifecycle()
            when (val current = state) {
                is AuthState.Loading -> CircularProgressIndicator()
                is AuthState.LoggedIn -> Button(onClick = viewModel::logout) { Text("Log Out") }
                is AuthState.Error -> Text("Something went wrong: ${current.message}")
                AuthState.LoggedOut -> Button(onClick = viewModel::login) { Text("Log In") }
            }
        }
    }
    ```
  </Step>

  <Step title="Show the user profile" stepNumber={7}>
    After login, call the Authentication API's `userInfo` with the access token to retrieve the authenticated user's profile.

    ```kotlin composeApp/src/commonMain/kotlin/AuthViewModel.kt theme={null}
    import com.auth0.kmp.authentication.UserInfo

    suspend fun fetchProfile(accessToken: String): UserInfo? {
        return when (val result = auth0.authentication.userInfo(accessToken)) {
            is Result.Success -> result.data // .name, .email, .picture, .customClaims, ...
            is Result.Failure -> null
        }
    }
    ```

    <Info>
      On app launch, you can skip the login screen if valid credentials already exist: `credentialsManager.hasValidCredentials()` returns `true` when a stored, non-expired session is available. Use `credentialsManager.getCredentials()` to retrieve a valid access token, and renew the access token with the [refresh token](/docs/secure/tokens/refresh-tokens) automatically when needed.
    </Info>
  </Step>

  <Step title="Run your app" stepNumber={8}>
    Build and launch on each target:

    <CodeGroup>
      ```shellscript Android theme={null}
      ./gradlew :composeApp:installDebug
      ```

      ```shellscript iOS theme={null}
      # Open the Xcode project and run on a simulator or device
      open iosApp/iosApp.xcodeproj
      ```
    </CodeGroup>

    **Expected flow:**

    1. App launches with a "Log In" button.
    2. Tap "Log In" → the system browser opens the Auth0 Universal Login page → complete login.
    3. Control returns to the app automatically and the button switches to "Log Out".
    4. Success!
  </Step>
</Steps>

<Check>
  **Checkpoint**

  You now have a Compose Multiplatform app with Auth0 login, logout, secure token storage, and user profile retrieval — sharing all authentication logic between Android and iOS.
</Check>

***

## Troubleshooting & Advanced

<Accordion title="Callback URL mismatch error">
  **Cause:** The callback URL the SDK generates isn't listed in your Auth0 application, or the scheme/package don't match.

  **Fix:** Confirm Allowed Callback URLs in Application Settings exactly matches the platform format — `SCHEME://YOUR_DOMAIN/android/APPLICATION_ID/callback` for Android and `SCHEME://YOUR_DOMAIN/ios/BUNDLE_ID/callback` for iOS. The `SCHEME` (default: your application ID) and package/bundle must be identical to your project's values. URLs are case-sensitive and the scheme must be lowercase.
</Accordion>

<Accordion title="The browser opens but never returns to the app">
  **Cause:** The callback scheme isn't declared on the platform, so the OS can't route the redirect back to your app.

  **Fix:** On Android, verify `manifestPlaceholders["auth0Scheme"]` and `["auth0Domain"]` are set in `composeApp/build.gradle.kts` and run a clean build. On iOS, verify the `CFBundleURLSchemes` entry in `Info.plist` matches your bundle identifier.
</Accordion>

<Accordion title="Login returns Result.Failure with a network or timeout error">
  **Cause:** The device can't reach your Auth0 tenant, or a request times out.

  **Fix:** Confirm `domain` in `Auth0Account` is your tenant domain without the `https://` scheme (e.g., `your-tenant.us.auth0.com`). On a physical device, ensure it has network access. You can raise the timeouts via the `NetworkingConfiguration` passed to `Auth0Account`.
</Accordion>

<Accordion title="getCredentials fails after login">
  **Cause:** No refresh token is issued, so expired credentials aren't renewed.

  **Fix:** Universal Login requests the `offline_access` scope by default (which returns a refresh token). If you override `scope` in `LoginOptions`, include `offline_access`, and enable **Refresh Token Rotation** for the application in the Auth0 Dashboard.
</Accordion>

<Accordion title="Android build fails to merge the RedirectActivity">
  **Cause:** The `auth0Domain` / `auth0Scheme` manifest placeholders are missing, so the SDK's merged `RedirectActivity` has no value to bind to.

  **Fix:** Ensure both placeholders are defined in the `defaultConfig` block of `composeApp/build.gradle.kts`. If you use multiple build flavors, define them in each flavor.
</Accordion>

<Accordion title="Restore a session on startup">
  Check for stored credentials before showing the login screen so returning users skip Universal Login:

  ```kotlin theme={null}
  if (credentialsManager.hasValidCredentials()) {
      val credentials = credentialsManager.getCredentials() // auto-renews if expired
      // route to your authenticated screen
  }
  ```
</Accordion>

<Accordion title="Call your own API with an access token">
  Request an audience so Auth0 issues an access token for your API:

  ```kotlin theme={null}
  import com.auth0.kmp.webauth.LoginOptions

  auth0.webAuth.login(
      LoginOptions(
          audience = "https://your-api.example.com", // From API Settings → Identifier
          scope = "openid profile email offline_access",
      ),
  )
  ```
</Accordion>

<Accordion title="Federated logout">
  Log the user out of the upstream identity provider as well as Auth0:

  ```kotlin theme={null}
  import com.auth0.kmp.webauth.LogoutOptions

  auth0.webAuth.logout(LogoutOptions(federated = true))
  ```
</Accordion>

## Next steps

* Explore the full [Auth0 Kotlin Multiplatform SDK](https://github.com/auth0/auth0-kmp) and its `EXAMPLES.md` for organizations, DPoP, passkeys, and custom storage.
* Run the complete [Android + iOS sample app](https://github.com/auth0/auth0-kmp/tree/main/sample-app).
* Learn more about [Auth0 Universal Login](/docs/authenticate/login/auth0-universal-login) and [refresh tokens](/docs/secure/tokens/refresh-tokens).
