secure-storage

Keep login tokens and secrets in the phone or computer's secure vault instead of plain storage.

Device featuresClient Server

Session tokens and API keys shouldn't sit in localStorage, where any script on the page can read them. Each platform has a proper vault, and each one has a different API. secureStorage is one async key-value API over all of them: the iOS Keychain and Android Keystore through the mobile package, Electron's safeStorage or the Tauri stronghold on desktop, and on the web an AES-GCM fallback whose key never leaves the browser's key store. Code that stores a session token reads the same everywhere.

Install

kitr add secure-storage

First time? Run npm i -g kitr once. Inside a project, the global kitr runs your project's own version.

// config/secure-storage.ts
import { defineConfig } from 'kitr/secure-storage'

export default defineConfig({
  prefix: 'myapp:',
  requireSecure: false, // true: throw instead of using the web fallback
})

Typed items

// app/lib/session.ts
import { defineSecureItem } from 'kitr/secure-storage'
import { z } from 'zod'

export const sessionItem = defineSecureItem('session', {
  schema: z.object({ token: z.string(), expiresAt: z.number() }),
})
import { useSecureItem } from 'kitr/secure-storage/react'

function Account() {
  const [session, setSession, { loading, secure }] = useSecureItem(sessionItem)
  if (loading) return null
  if (!session) return <SignIn onDone={(s) => setSession(s)} />
  return <p>Signed in{secure ? '' : ' (browser storage)'} · <button onClick={() => setSession(undefined)}>Sign out</button></p>
}

Outside React: app.container.use('secureStorage').get('session') or sessionItem(storage).get(). Values that fail the schema are removed, so a stale shape after an upgrade falls back to signed out rather than crashing.

Drivers

Platform packages register the vault under the secure-storage capability; secureStorage.secure says whether one is active. On the server the service is an in-memory store, so shared code needs no environment check.

Where values are kept

Where it runs Driver
iOS, Android The Keychain and the Keystore, through the mobile package
Desktop Electron's safeStorage or the Tauri stronghold, through the desktop package
Browser AES-GCM encrypted in local storage, with a key the browser keeps non-exportable. secure is false.
Server In memory, so shared code runs unchanged

The browser fallback stops a casual look at local storage. It can't stop code running on your own page, so a web app that stores tokens should still keep its pages free of untrusted scripts. Set requireSecure: true to refuse the fallback altogether.

Option Default Description
prefix kitr:secure: Prefix for every key.
requireSecure false Throw instead of using the browser fallback.
driver the platform's vault Replace the vault, for tests or custom hardware.
storage the app's local storage Where the browser fallback keeps its ciphertext.

Tests

import { createMemorySecureStorageDriver, createSecureStorage } from 'kitr/secure-storage'

const storage = createSecureStorage({ driver: createMemorySecureStorageDriver() })