@identsphere/core
v0.2.1
Published
Framework-agnostic, zero-dependency TypeScript client for the IdentSphere authentication API. Runs anywhere fetch exists: browser, React Native, Node, Deno, Bun, Workers.
Maintainers
Readme
@identsphere/core
Framework-agnostic, zero-dependency TypeScript client for the
IdentSphere authentication API. It has no React, no
DOM, and no runtime dependencies — it runs anywhere fetch exists: the
browser, React Native, Node 18+, Deno, Bun, and Cloudflare Workers.
Reach for this package when the DOM-bound @identsphere/react binding doesn't
fit your runtime — a React Native / Tamagui app, a non-React framework, a
backend-for-frontend, or an edge worker.
npm install @identsphere/coreQuick start
import { IdentSphereClient } from "@identsphere/core";
const auth = new IdentSphereClient({
baseUrl: "https://auth.example.com",
mode: "bearer", // "bearer" for native; "cookie" for first-party web
});
const result = await auth.login({ email, password });
if (result.status === "mfa_required") {
await auth.completeMfaChallenge({ mfa_token: result.mfa_token, code: "123456" });
}
// authenticated — call any protected endpoint with auth + auto-refresh applied:
const me = await auth.request("GET", "/v1/users/me");Transport modes
| Mode | Use for | Auth | CSRF | Refresh token |
| --- | --- | --- | --- | --- |
| cookie | First-party web | httpOnly identsphere_at cookie | double-submit identsphere_csrf header | httpOnly cookie (JS never sees it) |
| bearer | React Native, native, edge | Authorization: Bearer <access> | n/a (bearer bypasses CSRF) | httpOnly cookie via the platform cookie jar |
credentials: "include" is sent in both modes so the refresh cookie rides
along. On React Native the native networking layer (bare RN and Expo) persists
and re-sends cookies automatically, so refresh() works out of the box.
Runtime note:
refresh()relies on a cookie jar to carry the httpOnlyidentsphere_rtcookie. Browsers and React Native have one; bare Node / Workers do not, so thererefresh()requires either a cookie-jarfetchwrapper or the server's opt-in body-token delivery. See Universal & React Native in the docs.
Token storage
The access token's home is injected, never assumed:
// React Native (Expo):
import * as SecureStore from "expo-secure-store";
import { createKeyValueTokenStore } from "@identsphere/core";
const tokenStore = createKeyValueTokenStore(SecureStore, "identsphere.at");
const auth = new IdentSphereClient({ baseUrl, mode: "bearer", tokenStore });MemoryTokenStore (the default) and createKeyValueTokenStore(...) (wrap
expo-secure-store, AsyncStorage, react-native-keychain, or
localStorage) ship in the box. The refresh token is never exposed to JS —
it stays an httpOnly cookie by design.
API
The client mirrors the REST surface with typed methods:
- Credentials —
register,login,completeMfaChallenge - Passwordless —
requestEmailOtp,verifyEmailOtp - Session —
bootstrap,getSession,refresh,logout - Password reset —
forgotPassword,resetPassword - MFA management —
getMfaStatus,setupMfa,enableMfa,disableMfa,regenerateRecoveryCodes,stepUpMfa - Escape hatch —
request(method, path, opts)runs any endpoint through the same auth + single-flight refresh-and-retry engine - State —
getState(),subscribe(listener),isAuthenticated()
login() and verifyEmailOtp() return a discriminated union — branch on
result.status === "mfa_required" to drive the second-factor step.
Every rejection is an IdentSphereError with a stable machine code and
predicates (isMfaRequired, isAuthRequired, isRateLimited, isCsrfFailed,
isStepUpRequired), so you branch on codes, not error strings.
License
BUSL-1.1 © Pradumna Gautam. See LICENSE.
