React and Next.js

@okia/react — provider, hooks, and Next.js server helpers. Sole dependency: jose.

npm install @okia/react

Exports

Entry Exports Runs
@okia/react OkiaProvider, LoginButton, useOkia client
@okia/react/next createOkiaHandlers (catch-all route: login|callback|logout|session), getOkiaSession, middleware guard server, edge-compatible
@okia/react/server verifyOkiaToken (jose JWKS verify: checks iss, aud, exp, token_use) server

Session: encrypted stateless JWT cookie (jose A256GCM, keyed by OKIA_COOKIE_SECRET), httpOnly, 7 d rolling, edge-compatible. No refresh tokens in MVP — re-auth is one Face ID tap.

Env vars: OKIA_CLIENT_ID, OKIA_CLIENT_SECRET, OKIA_COOKIE_SECRET. (OKIA_AUTH_ORIGIN too, if you are pointing at anything other than https://auth.okia.io.)

Optional: OKIA_APP_ORIGIN (or the appOrigin option) pins the callback URL to your public origin when the request cannot know it — a proxy that strips forwarded headers, or a tunnel in front of next dev. Unset, the callback is derived from each request, which is right for almost everyone.

Generate the cookie secret with openssl rand -base64 32. It encrypts the session cookie, so rotating it signs everyone out — which is also how you sign everyone out.

Sketch 1: App Router route

One catch-all route wires login, callback, logout, and session:

// app/api/okia/[action]/route.ts
import { createOkiaHandlers } from "@okia/react/next";

export const { GET, POST } = createOkiaHandlers({
  clientId: process.env.OKIA_CLIENT_ID!,
  clientSecret: process.env.OKIA_CLIENT_SECRET!,
});

Register https://yoursite.com/api/okia/callback as the redirect URI in the dashboard. Choosing "React or Next.js" when you create the site fills that in, and registers http://localhost:3000/api/okia/callback beside it so next dev signs in on the first run — edit or clear it on the form if your dev server lives elsewhere.

Sketch 2: provider + hook

// app/layout.tsx (or your root)
import { OkiaProvider, LoginButton, useOkia } from "@okia/react";

function Account() {
  const { user, logout } = useOkia();
  if (!user) return <LoginButton />;
  return <button onClick={logout}>Sign out ({user.sub})</button>;
}

export default function Root({ children }: { children: React.ReactNode }) {
  return (
    <OkiaProvider>
      <Account />
      {children}
    </OkiaProvider>
  );
}

user.sub is your site's pairwise subject (pw_…) — stable for this site, meaningless anywhere else. Check user.amr if you want to force step-up after an OTP fallback login (amr: ["otp"]).

In a Server Component, skip the hook and read the session directly:

import { getOkiaSession } from "@okia/react/next";

export default async function Page() {
  const session = await getOkiaSession();
  return <p>{session ? session.user.sub : "Signed out"}</p>;
}

Hand the provider what the server knows and the first paint is final — no /api/okia/session request, no "loading" flash on a signed-out page:

// app/layout.tsx
import { OkiaProvider } from "@okia/react";
import { getOkiaSession } from "@okia/react/next";

export default async function Root({ children }: { children: React.ReactNode }) {
  const user = (await getOkiaSession())?.user ?? null;
  return <OkiaProvider initialUser={user}>{children}</OkiaProvider>;
}

initialUser has three states: a user (signed in), null (the server checked — signed out), and omitted (unknown — the provider asks the session route once). Since 2.0.0 null means signed out, not unknown; if you passed null to mean "go and check", drop the prop.

Sketch 3: middleware guard

// middleware.ts
import { okiaMiddleware } from "@okia/react/next";

export const middleware = okiaMiddleware();
export const config = { matcher: ["/dashboard/:path*"] };

It remembers where the visitor was going and sends them back there after they sign in. Runs on the Edge runtime — the session cookie is decrypted with Web Crypto, so there is nothing Node-only in the path. Write it by hand with getOkiaSession(req) if you want different behaviour.

Server-side token verification (API routes, custom Node backends):

import { verifyOkiaToken } from "@okia/react/server";

const claims = await verifyOkiaToken(idToken); // throws unless iss, aud, exp, and token_use === "id" all pass

Install to working login (~8 min)

  1. Dashboard → your site → copy client ID and secret; register /api/okia/callback as the redirect URI.
  2. npm install @okia/react; set OKIA_CLIENT_ID, OKIA_CLIENT_SECRET, OKIA_COOKIE_SECRET.
  3. Add the catch-all route (sketch 1).
  4. Wrap your app in OkiaProvider and drop in LoginButton (sketch 2).
  5. Optional: add the middleware guard for protected paths (sketch 3).
  6. Run the app, click the button, complete the WhatsApp code + passkey flow. getOkiaSession now returns your user.
React and Next.js · Okia docs