Kanzo UI
Auth

Auth

Keycloak's claims read into one Session, a role predicate that knows about organizations, and a Backend For Frontend for Next in one object. A sibling of @kanzo-tech/ui, never part of it.

@kanzo-tech/auth is what a product needs once there is somebody signed in: the claim vocabulary read into one session, the role evaluation that knows about organizations, and the Backend For Frontend that signs people in, keeps their session alive and ends it — kanzoAuth for Next.

It is a sibling of @kanzo-tech/ui, not part of it. It does not depend on the library, and nothing in the library may depend on it.

The one rule to read before anything else

What a client knows about its roles is for drawing, never for deciding.

The roles in a session are a copy, and a copy is something an attacker owns the moment it reaches the browser. Gate hides a control and can answers a question about what to draw; the resource server, validating the access token it was sent, is what refuses the request behind the button. A product gated only here is ungated.

The same rule one level down: never open the access token in the client. It is opaque to a client by definition, its format is not guaranteed to stay stable, and it may be encrypted for the resource — which is why depending on its contents is, in Microsoft's words for exactly this mistake, "one of the most common sources of errors and client logic breakage." Roles for drawing come from the session; the session comes from the ID token or from your own server.

This is physical rather than a preference, so there is no configuration that changes it and no future release that softens it. Every other page assumes it and points back here rather than arguing it again; the auth layer is why it is the first thing this page says, and what else follows from it.

Why it is not in the library

The first admission rule is domain-free — nothing about RDF / SHACL / fossil / graphs / auth. Auth is excluded by name, and there is a second, blunter reason that the root barrel makes structural: a consumer who wants a Button must not pay for a session. The auth layer has the composite that made the rule, and why this package depends on nothing of the library's.

What it is not

There is no sign-in screen, no user menu and no organization switcher. A sign-in screen is a logo, a legal line, a privacy notice and a button, and every product answers those differently — @kanzo-tech/ui has the parts to draw all three, and the auth layer is why none of them is shipped from here.

There is no protocol in it either. The confidential client belongs to openid-client, behind ./server, and the browser holds a cookie and no token — RFC 10017's Backend For Frontend, the architecture it recommends for business applications. Writing OAuth by hand is where a mistake stops being a bug and becomes a vulnerability.

What is left after those two subtractions is the whole of the package: Keycloak's claims as one Session, a role predicate that understands organizations, and the session lifecycle around them — renewed and ended on the server, signed back in once in the browser.

Adding auth to a product

The server half is one object and four files — Next.js has them in full:

lib/auth.ts
import { kanzoAuth } from "@kanzo-tech/auth/next";

export const auth = kanzoAuth(async () => ({ clientId, clientSecret, issuer, secret, store }));
proxy.ts
export const proxy = (request: NextRequest) => auth.proxy(request);
app/api/auth/[...auth]/route.ts
export const { GET, POST } = auth.routes;

The browser half is one line and a provider:

providers.tsx
import { AuthProvider, bffAuth } from "@kanzo-tech/auth";

const auth = bffAuth();

<AuthProvider auth={auth}>
  <App />
</AuthProvider>;
api/client.ts
// Every request from here on carries the session, renews it once, and signs in when it is over.
createClient<paths>({ baseUrl: "/", fetch: auth.fetch });
anywhere
import { Gate, useSession } from "@kanzo-tech/auth";

const { session, can } = useSession();

<Gate role="warden">
  <Button>Post a contract</Button>
</Gate>;

bffAuth is built outside React, on purpose. A product's API client is a module rather than a component, and a fetch reachable only from a hook would drag every call site into the tree to get at it.

What a name means here

The shape of a name says what it gives you, and there are two families:

ShapeWhat it answersNames
<where>Autha whole side of the BFF in one objectbffAuth, the browser's Auth; kanzoAuth, the server's
anything elseitselfrelyingParty, issuer, sealedCookie, claims, can

The suffix is a promise, so the second family is not a leftover: the relying party answers the protocol's verbs — begin, complete, read, refresh, end — and a name ending in Auth would promise a side of the pattern it is only a part of. Relying party is the term the specification already uses for it.

Who is signed in

useSession is the hook, and status is the field to branch on — not session === null, which is the same answer for anonymous and we have not looked yet. Drawing a sign-in prompt during the second is the flicker every application with a session has shipped at least once. A session that could not be read is a fourth answer, "failed", with what was thrown on error — never anonymous, which would send the person to sign in over an outage.

Every example on these pages runs against a fabricated session rather than a realm, from one file — docs/lib/guild-auth.ts, which builds a claim set and runs the package's own reader over it, so what they draw is what a token would produce rather than a Session somebody typed. Auth is five members — getSession, subscribe, signIn, signOut and a fetch — which is what makes faking it possible at all.

What the token actually carries puts that claim set and the Session it becomes side by side, with the reading client id as a control: the machinery is on screen rather than described.

Installing it

pnpm add @kanzo-tech/auth

The root barrel carries no engine — react and nothing else — which is why the browser half of every product imports it.

DoorWhat it isPeer to install
@kanzo-tech/authTypes, the claim reader, can, the provider, the hooks, Gate and bffAuthnone
@kanzo-tech/auth/serverrelyingParty — the confidential client: exchange, sealed cookie, renewal, end-session, back-channel logout — and the session storesopenid-client, jose
@kanzo-tech/auth/nextkanzoAuth — the proxy, the routes, the forwarder and the server component's sessionopenid-client, jose, next 16

A door exists here only because it imports an engine, which is the engine rule the library's own subpaths follow. A consumer installs the peer for the door they open and no other.

pnpm add @kanzo-tech/auth openid-client jose

The two Node doors never reach React: they import the modules under the root barrel directly, so a server process does not pull a component library in behind an import of an OAuth client.

The realm it expects

The identity service is the server half — Keycloak with its realm as code — and it ships from this repository under the same tag as the package. What this package needs from it is small and none of it is invented: role claims on both tokens, an audience mapper naming your API, and the organization:* scope. Keycloak is that page, and it is the one to hand to whoever runs the realm.

On this page