Ionize Docs
CookbookTools

@nannier/sdk in your TypeScript app

Typed client for Ionize auth from Node / browser

The official @nannier/sdk package wraps Kratos and Hydra API calls with typed methods. Reduces boilerplate when integrating.

Install

npm install @nannier/sdk
# or
bun add @nannier/sdk

Initialize

import { IonizeClient } from "@nannier/sdk";

export const ionize = new IonizeClient({
  publicUrl: process.env.IONIZE_PUBLIC_URL!,     // https://ciam.your-domain
  adminUrl: process.env.IONIZE_ADMIN_URL,        // backend-only; omit for browser
  clientId: process.env.IONIZE_CLIENT_ID!,
  clientSecret: process.env.IONIZE_CLIENT_SECRET, // confidential clients only
});

Common use cases

Browser: get session

const session = await ionize.toSession();
// throws if no session; .traits, .identity_id, .aal available

Browser: start OAuth2 flow

const url = await ionize.buildAuthorizationUrl({
  scope: ["openid", "offline_access", "profile", "email"],
  redirectUri: "/callback",
  pkce: true,
});
window.location.href = url;

Browser: handle callback

const { accessToken, idToken, refreshToken } = await ionize.exchangeCodeForTokens(code);
// PKCE verifier is auto-managed in sessionStorage

Browser: refresh

const refreshed = await ionize.refreshToken();
// Stored access_token / refresh_token rotated

Server: introspect

const intro = await ionize.introspect(accessToken);
// intro.active, .sub, .scope, .client_id, ...

Server: list identities (admin)

const identities = await ionize.admin.identities.list({
  page: 1,
  perPage: 50,
  filter: 'traits.email eq "alice@example.com"',
});

Server: create identity

const identity = await ionize.admin.identities.create({
  schema_id: "default",
  traits: { email: "alice@example.com", first_name: "Alice" },
  credentials: {
    password: { config: { password: "RandomTempPass123!" } }
  },
  state: "active",
});

Server: trigger recovery

const link = await ionize.admin.identities.createRecoveryLink({
  identity_id: identityId,
  expires_in: "1h",
});
// Email this link to the user

Types

All responses are fully typed:

import type { Session, Identity, AdminIdentitiesListResponse } from "@nannier/sdk";

function describe(id: Identity) {
  return `${id.traits.first_name} ${id.traits.last_name}`;
}

Errors

The SDK throws typed errors:

import { IonizeError, NotFoundError, ValidationError } from "@nannier/sdk";

try {
  await ionize.toSession();
} catch (err) {
  if (err instanceof NotFoundError) {
    // not logged in
  } else if (err instanceof IonizeError) {
    // other Ionize error
  } else {
    throw err;
  }
}

React hooks (companion package)

@nannier/sdk-react:

npm install @nannier/sdk-react
import { IonizeProvider, useSession, useLogin } from "@nannier/sdk-react";

function App() {
  return (
    <IonizeProvider client={ionize}>
      <Profile />
    </IonizeProvider>
  );
}

function Profile() {
  const { session, loading } = useSession();
  if (loading) return <Spinner />;
  if (!session) return <a href="/login">Sign in</a>;
  return <p>Hi {session.identity.traits.first_name}</p>;
}

Server vs client

The SDK works both server-side (Node, Bun, Deno) and browser. But:

  • clientSecret should ONLY be set in server-side init.
  • Admin URL methods (ionize.admin.*) should ONLY be called server-side.
  • Browser calls use cookies; server calls use bearer tokens.

Don't import the same instance in both. Have two instances:

// server.ts
export const ionizeServer = new IonizeClient({ /* with admin */ });

// client.ts
export const ionizeBrowser = new IonizeClient({ /* without admin */ });

Frameworks

Next.js

// app/api/whoami/route.ts
import { ionizeServer } from "@/lib/ionize";
import { cookies } from "next/headers";

export async function GET() {
  const session = await ionizeServer.toSession(cookies().toString());
  return Response.json(session);
}

Express / Hono

app.use(ionize.middleware());  // sets req.session

Other

Any HTTP framework, call SDK methods in handlers.

Versioning

SDK version tracks Ionize core. SDK v1.4 works with Ionize 1.4.

For major changes (Hydra 2.x → 3.x), SDK bumps. Read CHANGELOG.

Bundle size

SDK is ~30 KB minified for client. Tree-shakeable, only what you use.

Server: no bundle concern.

Source

@nannier/sdk lives in sdk repo. PRs welcome.

On this page