Auth
createClient({ auth }) → api.auth — cookie-first, authorize/Can UI-only, Gate remains real authz.
createClient(url, { auth }) attaches api.auth — session orchestration over the same /auth/*
Flows. Prefer that one call. createAuthClient + bind stays as a compose escape hatch.
For storefronts and SPAs attaching identity to typed calls.
The one rule
Gate on Flows is real authorization. authorize / hasScope / Can are UI-only. Cookie mode
needs the csrf plugin (server soft-requires it when gate.auth.cookies.enabled); never persist
tokens to Storage when cookies own the session.
Smallest Example
One createClient with session auth
import { createClient } from "okengine/client";
import { vault } from "okengine/vault";
import { app } from "../../src/app";
export const api = createClient(app, vault.env("PUBLIC_API_URL") ?? "", {
auth: { mode: "cookie", csrfConfigured: true },
});api.auth is an AuthClient. Unit Flows remain reachable (api.auth.me, …) under the same
namespace.
Sign in and authorize chrome
const result = await api.auth.signIn.email({ email: "alex@acme.co", password });
if (!result.ok) {
if (result.twoFactor) {
await api.auth.completeChallenge({
challengeId: result.twoFactor.challengeId,
code: totp,
});
}
return;
}
const gate = api.auth.authorize({ all: ["orders:write"] });
if (gate.status === "allowed") showFulfillment();
await api.auth.signOut();Progressive Patterns
const gate = api.auth.authorize({ all: ["orders:write"] });
// or: api.auth.authorize({ any: ["orders:write", "admin"] })
if (gate.status === "unauthenticated") redirectToSignIn();
if (gate.status === "loading") return;
if (gate.status === "denied") showMissing(gate.missing);
// gate.status === "allowed"React: <Can auth={api.auth} all={["orders:write"]} fallback={<Denied />}>. Prefer
authorize / Can over bare hasScope. On server Forbidden, use isForbidden /
forbiddenScopes and refresh getSession() if scopes look stale.
Helpers
| Helper | Package | Role |
|---|---|---|
createClient({ auth }) | okengine/client | Happy path — attaches api.auth |
createAuthClient | okengine/client/auth | Compose / bind escape hatch |
tokenFromRequestCookies / createServerClient | same | SSR cookie → token |
authorize / hasScope / can | on AuthClient | UI-only chrome |
isUnauthorized / isCsrf / forbiddenScopes | same | Envelope narrowers |
useSession / useAuthorize / Can | okengine/client-react | React session + chrome |
Denial codes
| Code | HTTP | Typical fix |
|---|---|---|
Unauthorized | 401 | Sign in or refresh; re-login if still denied |
Forbidden | 403 | Wrong scopes / CSRF — read error.data.reason |
RateLimited | 429 | Wait retryAfterMs |
Also see CORS and CSRF. Advanced create-oke starter
demos cookie + passkey + <Can>.
Troubleshooting
Plug csrf({ allowNoHeader: false }) when gate.auth.cookies.enabled. Set
csrfConfigured: true on the client only after the plugin is installed.
Expected — client authorize is UI-only. Add gate.scope / gate.policy on the Flow.
Prefer cookie mode or memory / sessionStorage. XSS can read Storage until family revoke.
Learn more
- Calling — envelopes, binary
{ response: "blob" }, REST - React —
useSession/Can/useAuthorize - Gate · Auth — server cookies and scopes