@kiavi/kiavi-react-native
Ce contenu n’est pas encore disponible dans votre langue.
0.1.0-alpha.1React Native client for Kiavi-hosted authentication. Use this in Expo or bare React Native apps (iOS and Android) when you need to sign users in against a Kiavi auth server and attach their access token to outgoing API calls.
This page mirrors the llm-docs.md that ships with the installed package, that file is the authoritative source. If anything here ever drifts, trust the package version.
Speaks the same /api/auth/exchange/* wire protocol as @kiavi/kiavi-browser. PKCE only, no client secrets. Refresh tokens are stored in the OS keychain (iOS Keychain / Android Keystore) via expo-secure-store.
Install
Section titled “Install”pnpm add @kiavi/kiavi-react-native expo-auth-session expo-secure-store expo-cryptoexpo-auth-session brings expo-web-browser in transitively. If you have a bare workflow that pins peer deps, install it explicitly too.
Then run the init command to install the version-pinned integration guide for AI coding agents into your repo:
npx kiavi-react-native initThis writes .kiavi/kiavi-react-native.md and adds a reference block to your AGENTS.md (or CLAUDE.md). Re-run on every upgrade. See AI agent docs for the full explanation.
Configure your app’s URL scheme
Section titled “Configure your app’s URL scheme”The SDK derives its redirect URI from clientId by stripping a trailing -native suffix: yourapp-native → yourapp://auth. Add the matching scheme to app.json:
{ "expo": { "scheme": "yourapp", "ios": { "bundleIdentifier": "com.yourapp.app" }, "android": { "package": "com.yourapp.app" } }}If you need a different scheme or a Universal Link / App Link, pass redirectUri explicitly. Whatever you pass must match an entry in the client’s allowed_origins (configured in the Kiavi management UI). The auth server only allows the derived scheme prefix for that client.
If your clientId does not end in -native, the constructor throws KiaviAuthError with code: 'invalid_request', pass redirectUri explicitly to opt out of derivation.
Minimum viable integration
Section titled “Minimum viable integration”Three things, in order:
- Create one
KiaviClientinstance and export it. - Call
authenticate()from a sign-in button to ensure the user is signed in. This opens the system browser if needed. - Call
getAccessToken()before each API call and attach it as aBearertoken.
import { KiaviClient } from '@kiavi/kiavi-react-native'
export const kiavi = new KiaviClient({ authBaseUrl: 'https://yourapp.kiavi.eu', clientId: 'yourapp-native', // redirectUri: 'yourapp://auth', derived automatically})// sign-in screenimport { Button } from 'react-native'import { kiavi } from '@/lib/kiavi'import { useRouter } from 'expo-router'
export default function SignInScreen() { const router = useRouter() return ( <Button title='Sign in' onPress={async () => { await kiavi.authenticate() router.replace('/home') }} /> )}// any API callimport { kiavi } from '@/lib/kiavi'
const token = await kiavi.getAccessToken()const res = await fetch('https://api.yourapp.com/me', { headers: { Authorization: `Bearer ${token}` },})That is the full happy path. authenticate() is idempotent: if a session is already live or silently refreshable from secure storage, it returns immediately without opening the browser. Otherwise it opens the system browser (SFSafariViewController on iOS, Custom Tabs on Android) and resolves once the user returns via the deep link.
Options
Section titled “Options”new KiaviClient({ authBaseUrl: 'https://yourapp.kiavi.eu', clientId: 'yourapp-native', redirectUri: 'yourapp://auth', // optional, derived from clientId by default debug: true, // logs to console; pass a function for structured routing})authBaseUrl: string, required. The base URL of the Kiavi auth server.clientId: string, required. The OIDC client ID registered for this app. Must end in-nativeif you want the redirect URI auto-derived.redirectUri?: string, override the default${scheme}://authderived fromclientId. Use this for Universal Links / App Links or other deep-link conventions. Must match an entry in the client’sallowed_origins.debug?: boolean | KiaviDebugLogger,trueenables the built-in console logger. A function receives structuredKiaviDebugLogEntryobjects so you can route logs to your own observability tooling.
Methods
Section titled “Methods”authenticate(): Promise<KiaviSession>
Section titled “authenticate(): Promise<KiaviSession>”Ensure the user is signed in. Resolution order:
- Live in-memory session that is not expiring soon, return it.
- Refresh token in secure storage, silently mint a new access token and return.
- Otherwise, open the system browser to the authorize endpoint, wait for the deep-link callback, exchange the code, and persist the new refresh token.
Concurrent callers share a single in-flight authenticate() promise. Throws KiaviAuthError if the browser session is dismissed, the redirect is malformed, or the server rejects the exchange.
getAccessToken(): Promise<string>
Section titled “getAccessToken(): Promise<string>”Return a valid JWT for an Authorization header. Refreshes automatically when the access token has less than 30 seconds of life left. Concurrent callers share one in-flight refresh.
Throws KiaviSessionExpiredError if there is no session and refresh fails. Catch that and call authenticate() to start a new sign-in flow.
import { KiaviSessionExpiredError } from '@kiavi/kiavi-react-native'
try { const token = await kiavi.getAccessToken() // ...} catch (err) { if (err instanceof KiaviSessionExpiredError) { await kiavi.authenticate() } else { throw err }}getSession(): Promise<KiaviSession | null>
Section titled “getSession(): Promise<KiaviSession | null>”Non-redirecting read of the current session. Awaits any in-flight initialization, then returns the live session or null. Use this on app launch to decide whether to render the signed-in or signed-out shell without triggering the browser.
signOut(): Promise<void>
Section titled “signOut(): Promise<void>”Revokes the refresh token server-side, wipes secure storage, and notifies listeners. There is no browser redirect on mobile, handle navigation in your onAuthStateChange listener or after signOut() resolves.
onAuthStateChange(listener): () => void
Section titled “onAuthStateChange(listener): () => void”Subscribe to session changes. Fires on every successful sign-in, every refresh, every sign-out, and every refresh failure that clears the session (for example, the user revoked this device from another device). Does not fire synchronously on subscription, call getSession() first if you need the current state. Returns an unsubscribe function.
useEffect(() => { return kiavi.onAuthStateChange((session) => { if (!session) router.replace('/sign-in') })}, [])Errors
Section titled “Errors”import { KiaviAuthError, KiaviSessionExpiredError, KiaviBrowserOnlyError,} from '@kiavi/kiavi-react-native'KiaviAuthError
Section titled “KiaviAuthError”Thrown by authenticate(), getAccessToken() (during refresh), and signOut() (during revoke) on any non-success response. Fields:
code: KiaviAuthErrorCode,'rate_limited' | 'invalid_request' | 'unauthenticated' | 'server_error' | 'network_error' | 'unknown'status: number, HTTP status, or0for network errors / invalid inputretryAfterSeconds?: number, parsed from theRetry-Afterheader on 429 responsesmessage: string, server-provided message when available, otherwise a generic description
Network errors (offline, DNS failure) surface as KiaviAuthError with code: 'network_error' and status: 0. They never wipe the user’s saved refresh token, only an unauthenticated (401) response from /refresh does that, since it means the chain has been server-side revoked.
KiaviSessionExpiredError
Section titled “KiaviSessionExpiredError”Thrown by getAccessToken() when there is no session and refresh cannot recover one. Catch and call authenticate() to start a new sign-in flow.
KiaviBrowserOnlyError
Section titled “KiaviBrowserOnlyError”Exported for cross-SDK API parity with @kiavi/kiavi-browser. The native SDK never throws this; it exists so consumer code that catches both can compile against either package.
Exported from @kiavi/kiavi-react-native:
KiaviClient, the class aboveKiaviSession,{ token: string, expiresAt: number | null, user: KiaviSessionUser | null }KiaviSessionUser,{ id: string, email: string, emailVerified?: boolean, name: string | null }KiaviClientOptions,KiaviAuthenticateOptionsKiaviAuthStateListener,(session: KiaviSession | null) => voidKiaviAuthError,KiaviAuthErrorCodeKiaviSessionExpiredErrorKiaviBrowserOnlyError(parity-only, never thrown by this package)KiaviDebugLogger,KiaviDebugLogEntry
Do / do not
Section titled “Do / do not”Do:
- Create exactly one
KiaviClientinstance per app and export it as a module singleton. - Call
getAccessToken()inline on every API request, it’s fast and handles refresh. - Call
getSession()on app launch to decide between signed-in and signed-out shells without triggering the browser. - Let
authenticate()be the only entry point that may open the browser. - Pair this with
@kiavi/kiavi-json your backend to verify the JWTs this client produces.
Do not:
- Do not implement your own refresh loop or token cache.
getAccessToken()is already silent, automatic, and de-duped across concurrent callers. - Do not write the refresh token anywhere yourself, the client owns secure storage.
- Do not catch
KiaviAuthErrorwithcode: 'network_error'and clear local state; that’s just the user being offline. Onlyunauthenticatedfrom/refreshindicates a revoked chain, and the client already handles that internally. - Do not pass
clientIdvalues that don’t end in-nativewithout also passingredirectUri, the constructor will throw.
Bare React Native (non-Expo) notes
Section titled “Bare React Native (non-Expo) notes”expo-auth-session, expo-secure-store, expo-crypto, and expo-web-browser all support bare RN via expo install. Follow each package’s installation steps; no other changes are needed.