# @authbase/node

> @authbase/node: a typed client, offline token verification and Express, Hono and Next.js helpers.

authbase for Node backends (Node 20.19+, ESM): a typed client for the
end-user API and offline access-token verification, with helpers for
Express, Hono and Next.js. Not on npm yet (it will be at the public
release): install it from a packed tarball.

```sh
cd sdk/typescript && npm ci && npm run build && npm pack   # → authbase-node-0.1.0.tgz
cd your-app && npm install ../authbase/sdk/typescript/authbase-node-0.1.0.tgz
```

## The client

One per app, with the app's API key from the environment (never in code):

```ts
import { Authbase, AuthbaseError } from "@authbase/node";

const authbase = new Authbase({ url: process.env.AUTHBASE_URL!, apiKey: process.env.AUTHBASE_API_KEY! });

await authbase.users.create({ email, password });
const tokens = await authbase.auth.login({ identifier: email, password, client_ip: req.ip });
const next = await authbase.auth.refresh({ refresh_token: tokens.refresh_token });
await authbase.auth.logout({ refresh_token: next.refresh_token });
```

Every method throws `AuthbaseError` on a non-2xx answer: `status`, `code`
(branch on this), `detail`, `requestId`, `errors` (per field, for 422) and
`retryAfter` (429/503). An unreachable server is `code: "network_error"`.

## Verifying access tokens

Tokens are verified locally against the app's JWKS: fetched once, cached
for 5 minutes, refetched (at most once a second) when a token carries an
unknown `kid`, so a signing-key rotation needs no restart. Only `EdDSA`,
`typ: at+jwt`, the app's issuer and the app id as `aud` are accepted.

```ts
const claims = await authbase.verifyAccessToken(token); // learns issuer and app id once from GET /v1/app
```

A service that only checks tokens needs no API key:

```ts
import { createVerifier } from "@authbase/node";
const verify = createVerifier({ issuer: "https://auth.example.com/apps/cashmate", appId: "01a0…" });
```

A bad token throws `AuthbaseTokenError` (`token_expired`, worth a refresh,
or `token_invalid`). A JWKS that cannot be fetched throws something else:
that is an outage, not the caller's fault.

## Framework helpers

Each takes the client or `{ issuer, appId }`, and optionally `{ roles }`
(all required). Missing or bad tokens get 401 with `WWW-Authenticate:
Bearer` (RFC 6750), missing roles 403, an unreachable JWKS 503; bodies are
problem+json.

```ts
// Express
import { requireAuth } from "@authbase/node/express";
app.get("/me", requireAuth(authbase), (req, res) => res.json({ id: req.auth!.sub }));

// Hono
import { requireAuth, type AuthVariables } from "@authbase/node/hono";
const app = new Hono<{ Variables: AuthVariables }>();
app.get("/me", requireAuth(authbase, { roles: ["admin"] }), (c) => c.json({ id: c.get("auth").sub }));

// Next.js route handler (app/api/me/route.ts)
import { withAuth } from "@authbase/node/next";
export const GET = withAuth(authbase, async (req, { auth }) => Response.json({ id: auth.sub }));
```

## Development

`npm run check` regenerates the client from `api/openapi/openapi.yaml`
(and fails if the committed copy is stale), typechecks, tests and builds.
`AUTHBASE_TEST_URL=http://localhost:8080 npm test` also runs the
integration test against a real instance.
