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

# Introduction

> Integrate the Prelude Auth SDK into your mobile application.

This guide covers how to use the Prelude mobile SDKs to authenticate users on iOS, Android, Flutter, and React Native. The mobile SDKs persist tokens in the platform's secure store and bind them to the device with [DPoP](https://datatracker.ietf.org/doc/html/rfc9449).

Pick your platform once: tabs across this section stay in sync.

## Prerequisites

Before you start, make sure you have:

* A Prelude account with access to the Auth API — [contact us](mailto:support@prelude.so) to obtain access
* An **Application ID** (`appID`) — see [Applications](/session/documentation/applications)
* An authentication method configured — see [Password Authentication](/session/documentation/integration-guide/password-authentication) or [OTP Login](/session/documentation/integration-guide/otp-login)

<Tabs>
  <Tab title="iOS">
    - Xcode 15 or later, targeting iOS 15 or later

    A [custom domain](/session/documentation/domain-names) is optional on iOS. The SDK is reachable at the per-application Prelude subdomain (`https://{app_id}.session.prelude.dev`).
  </Tab>

  <Tab title="Android">
    * Android Studio with `minSdk` 26 or higher

    A [custom domain](/session/documentation/domain-names) is optional on Android. The SDK is reachable at the per-application Prelude subdomain (`https://{app_id}.session.prelude.dev`).
  </Tab>

  <Tab title="Flutter">
    * iOS 15.1 or higher / Android API 26 or higher

    The Dart API bridges to the native iOS (`PreludeAuth`) and Android (`so.prelude.android:auth-sdk`) SDKs, so tokens are persisted in the platform-native secure store and bound to the device with DPoP.
  </Tab>

  <Tab title="React Native">
    * Expo SDK 54 or higher (or bare React Native 0.81+)
    * iOS 15.1+ / Android API 26+ on-device

    The TypeScript API bridges to the native iOS (`PreludeAuth`) and Android (`so.prelude.android:auth-sdk`) SDKs, so tokens are persisted in the platform-native secure store and bound to the device with DPoP.
  </Tab>
</Tabs>

## Install the SDK

<Tabs>
  <Tab title="iOS">
    Add the Prelude Apple Auth SDK to your Xcode project as a Swift Package dependency:

    ```
    https://github.com/prelude-so/apple-auth-sdk
    ```

    In `Package.swift`:

    ```swift theme={null}
    dependencies: [
        .package(url: "https://github.com/prelude-so/apple-auth-sdk", from: "0.3.0"),
    ],
    targets: [
        .target(
            name: "YourApp",
            dependencies: [
                .product(name: "PreludeAuth", package: "apple-auth-sdk"),
            ]
        ),
    ]
    ```
  </Tab>

  <Tab title="Android">
    Add the Maven Central artifact to your module's `build.gradle.kts`:

    ```kotlin theme={null}
    dependencies {
        implementation("so.prelude.android:auth-sdk:0.3.0")
    }
    ```

    Or in Groovy:

    ```groovy theme={null}
    dependencies {
        implementation 'so.prelude.android:auth-sdk:0.3.0'
    }
    ```
  </Tab>

  <Tab title="Flutter">
    Add the package to your `pubspec.yaml`:

    ```yaml theme={null}
    dependencies:
      prelude_flutter_auth_sdk: ^0.4.0
    ```

    Then run `flutter pub get`, or in one line:

    ```bash theme={null}
    flutter pub add prelude_flutter_auth_sdk
    ```
  </Tab>

  <Tab title="React Native">
    Install the package with your package manager of choice:

    ```bash theme={null}
    npm install @prelude.so/react-native-auth-sdk@^0.2.1
    ```

    The SDK ships as an Expo module — Expo prebuild (or `expo run:ios` / `expo run:android`) wires the native iOS and Android plugins. No extra linking step.
  </Tab>
</Tabs>

## Initialize the SDK

Create a session client pointing at your application's domain.

<Tabs>
  <Tab title="iOS">
    ```swift theme={null}
    import PreludeAuth

    let client = try PreludeAuthClient(
        endpoint: .custom("https://{app_id}.session.prelude.dev")
    )
    ```

    `PreludeAuthClient` is a `Sendable` value type — copies share a single underlying actor, so it's safe to pass through `@State`, `@Environment`, or task groups.
  </Tab>

  <Tab title="Android">
    ```kotlin theme={null}
    import so.prelude.android.auth.PreludeAuthClient
    import java.net.URL

    val client = PreludeAuthClient(
        context = applicationContext,
        baseUrl = URL("https://{app_id}.session.prelude.dev"),
    )
    ```

    `PreludeAuthClient` is thread-safe — its mutable state lives behind internal coordinators, so it's safe to share a single instance across coroutines and ViewModels. Construct it off the main thread: instantiation hydrates the access-token cache from `SharedPreferences`, which is blocking I/O.
  </Tab>

  <Tab title="Flutter">
    ```dart theme={null}
    import 'package:prelude_flutter_auth_sdk/prelude_flutter_auth_sdk.dart';

    final client = PreludeAuthClient(
      endpoint: Endpoint.custom('https://{app_id}.session.prelude.dev'),
    );
    ```

    Each `PreludeAuthClient` instance represents a distinct session — DPoP keys, refresh tokens, and the access-token cache are scoped to that instance and managed by the native SDK. Most apps keep a single instance for the lifetime of the app.

    When you're done with an instance, call `dispose()` to release the native client:

    ```dart theme={null}
    await client.dispose();
    ```

    After `dispose()`, every other method on the instance throws `StateError`.
  </Tab>

  <Tab title="React Native">
    ```ts theme={null}
    import {
      Endpoint,
      PreludeAuthClient,
    } from "@prelude.so/react-native-auth-sdk";

    const client = new PreludeAuthClient({
      endpoint: Endpoint.custom("https://{app_id}.session.prelude.dev"),
    });
    ```

    Each `PreludeAuthClient` owns one logical session — the SDK forwards an opaque handle to the native side, which lazily creates one client per handle. Keep a single instance for the lifetime of your app (or per logical session).

    When you're done with an instance, call `dispose()` to release the native client:

    ```ts theme={null}
    await client.dispose();
    ```

    After `dispose()`, every other method on the instance throws `DisposedError`.
  </Tab>
</Tabs>

<Accordion title="Try it" icon="flask">
  <Tabs>
    <Tab title="iOS">
      Create a new SwiftUI app in Xcode, add the `PreludeAuth` package above, then replace `ContentView.swift` with:

      ```swift ContentView.swift theme={null}
      import SwiftUI
      import PreludeAuth

      // Replace with your application id.
      private let appID = "YOUR_APP_ID"

      @MainActor
      final class SessionStore: ObservableObject {
          let client: PreludeAuthClient

          init() {
              self.client = try! PreludeAuthClient(
                  endpoint: .custom("https://\(appID).session.prelude.dev")
              )
          }
      }

      struct ContentView: View {
          @StateObject private var store = SessionStore()

          var body: some View {
              VStack(spacing: 12) {
                  Text("Session Test").font(.title)
                  Text("SDK initialized.")
              }
              .padding()
          }
      }
      ```

      Build and run on the iOS simulator. If the SDK initialized cleanly, you're ready to wire up a login flow.
    </Tab>

    <Tab title="Android">
      Create a new Empty Compose app in Android Studio, add the dependency above, then replace `MainActivity.kt` with:

      ```kotlin MainActivity.kt theme={null}
      package com.example.sessiontest

      import android.os.Bundle
      import androidx.activity.ComponentActivity
      import androidx.activity.compose.setContent
      import androidx.compose.foundation.layout.*
      import androidx.compose.material3.*
      import androidx.compose.runtime.*
      import androidx.compose.ui.Alignment
      import androidx.compose.ui.Modifier
      import androidx.compose.ui.unit.dp
      import androidx.lifecycle.ViewModel
      import androidx.lifecycle.viewmodel.compose.viewModel
      import so.prelude.android.auth.PreludeAuthClient
      import java.net.URL

      // Replace with your application id.
      private const val APP_ID = "YOUR_APP_ID"

      class SessionViewModel(applicationContext: android.content.Context) : ViewModel() {
          val client: PreludeAuthClient = PreludeAuthClient(
              context = applicationContext,
              baseUrl = URL("https://$APP_ID.session.prelude.dev"),
          )
      }

      class MainActivity : ComponentActivity() {
          override fun onCreate(savedInstanceState: Bundle?) {
              super.onCreate(savedInstanceState)
              setContent {
                  MaterialTheme {
                      Surface {
                          Column(
                              Modifier.fillMaxSize().padding(24.dp),
                              verticalArrangement = Arrangement.Center,
                              horizontalAlignment = Alignment.CenterHorizontally,
                          ) {
                              Text("Session Test", style = MaterialTheme.typography.headlineMedium)
                              Text("SDK initialized.")
                          }
                      }
                  }
              }
          }
      }
      ```

      Build and run on an emulator or device. If the SDK initialized cleanly, you're ready to wire up a login flow.
    </Tab>

    <Tab title="Flutter">
      Create a new Flutter app, add the package above, then replace `lib/main.dart` with:

      ```dart lib/main.dart theme={null}
      import 'package:flutter/material.dart';
      import 'package:prelude_flutter_auth_sdk/prelude_flutter_auth_sdk.dart';

      // Replace with your application id.
      const appID = 'YOUR_APP_ID';

      void main() => runApp(const MyApp());

      class MyApp extends StatefulWidget {
        const MyApp({super.key});

        @override
        State<MyApp> createState() => _MyAppState();
      }

      class _MyAppState extends State<MyApp> {
        late final PreludeAuthClient client = PreludeAuthClient(
          endpoint: Endpoint.custom('https://$appID.session.prelude.dev'),
        );

        @override
        void dispose() {
          client.dispose();
          super.dispose();
        }

        @override
        Widget build(BuildContext context) {
          return MaterialApp(
            home: Scaffold(
              body: Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: const [
                    Text('Session Test', style: TextStyle(fontSize: 24)),
                    SizedBox(height: 8),
                    Text('SDK initialized.'),
                  ],
                ),
              ),
            ),
          );
        }
      }
      ```

      Run on an iOS simulator or Android emulator. If the SDK initialized cleanly, you're ready to wire up a login flow.
    </Tab>

    <Tab title="React Native">
      Scaffold a new Expo app, install the SDK, then replace `app/index.tsx` with:

      ```bash theme={null}
      npx create-expo-app@latest session-test --template blank-typescript
      cd session-test
      npm install @prelude.so/react-native-auth-sdk@^0.2.1
      ```

      ```tsx app/index.tsx theme={null}
      import {
        Endpoint,
        PreludeAuthClient,
      } from "@prelude.so/react-native-auth-sdk";
      import { useEffect, useState } from "react";
      import { Text, View } from "react-native";

      // Replace with your application id.
      const APP_ID = "YOUR_APP_ID";

      export default function Home() {
        const [client, setClient] = useState<PreludeAuthClient | null>(null);

        useEffect(() => {
          const c = new PreludeAuthClient({
            endpoint: Endpoint.custom(`https://${APP_ID}.session.prelude.dev`),
          });
          setClient(c);
          return () => { c.dispose().catch(() => {}); };
        }, []);

        return (
          <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
            <Text style={{ fontSize: 24 }}>Session Test</Text>
            <Text>{client ? "SDK initialized." : "Loading…"}</Text>
          </View>
        );
      }
      ```

      Run with `npx expo run:ios` or `npx expo run:android`. If the SDK initialized cleanly, you're ready to wire up a login flow.
    </Tab>
  </Tabs>
</Accordion>

## Helpers

<Tabs>
  <Tab title="iOS">
    No additional helpers are required — the iOS examples on subsequent pages can be used as-is.
  </Tab>

  <Tab title="Android">
    The Android Compose examples on subsequent pages call `viewModel(factory = viewModelFactory(applicationContext))` to hand the application context to a `ViewModel`. Drop this small helper into your project and reuse it across the examples:

    ```kotlin ViewModelFactory.kt theme={null}
    import android.content.Context
    import androidx.lifecycle.ViewModel
    import androidx.lifecycle.ViewModelProvider

    fun viewModelFactory(context: Context) = object : ViewModelProvider.Factory {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel> create(modelClass: Class<T>): T =
            modelClass.getConstructor(Context::class.java).newInstance(context) as T
    }
    ```
  </Tab>

  <Tab title="Flutter">
    No additional helpers are required — the Flutter examples on subsequent pages can be used as-is.
  </Tab>

  <Tab title="React Native">
    No additional helpers are required — the React Native examples on subsequent pages can be used as-is.
  </Tab>
</Tabs>
