# Hono

> Better Auth como camino principal, con el flujo OAuth manual como alternativa.


Hono corre en Node, Bun, Deno y en runtimes de borde. El SDK no usa APIs de Node, así que la misma integración sirve en todos.

## Con Better Auth

`@andco/better-auth` conecta Andco a Better Auth reutilizando la configuración de protocolo del SDK — es el camino recomendado si no tienes ya una estrategia OAuth propia.

```bash
npm install @andco/sdk @andco/better-auth better-auth hono
```

```ts title="src/auth.ts"
import { createAndcoOAuthProvider } from "@andco/better-auth";
import { createAndcoInstanceForServer } from "@andco/sdk/server";
import { betterAuth } from "better-auth";
import { genericOAuth } from "better-auth/plugins";

export const andco = createAndcoInstanceForServer({
  clientId: process.env.ANDCO_CLIENT_ID!,
  clientSecret: process.env.ANDCO_CLIENT_SECRET!,
});

export const auth = betterAuth({
  database: myDatabaseAdapter,
  plugins: [
    genericOAuth({
      config: [createAndcoOAuthProvider({ providerId: "andco", client: andco })],
    }),
  ],
});
```

El resto de las opciones — PAR automático, `loginHint`, `authorizations` por Resource Server — están en [`@andco/better-auth`](/docs/reference/andco-better-auth).

### Montar las rutas

Better Auth expone un `handler` estándar (`Request` → `Response`); montarlo en Hono es una línea:

```ts title="src/app.ts"
import { Hono } from "hono";
import { auth } from "./auth.js";

const app = new Hono();

app.on(["GET", "POST"], "/api/auth/*", (c) => auth.handler(c.req.raw));
```

Iniciar sesión, cerrar sesión y el callback quedan cubiertos por esas rutas — no escribes ninguna a mano.

### Ligar la credencial a la petición

El token de Andco vive en la cuenta vinculada de Better Auth, no en su sesión. `getAccessToken` lo resuelve y lo renueva si hace falta:

```ts title="src/middleware/andco.ts"
import type { AndcoBoundClient } from "@andco/sdk";
import { createMiddleware } from "hono/factory";
import { andco, auth } from "../auth.js";

export const withAndco = createMiddleware<{ Variables: { andco: AndcoBoundClient } }>(async (c, next) => {
  const session = await auth.api.getSession({ headers: c.req.raw.headers });
  if (!session) return c.json({ error: "unauthenticated" }, 401);

  const accounts = await auth.api.listUserAccounts({ headers: c.req.raw.headers });
  const andcoAccount = accounts.find((account) => account.providerId === "andco");
  if (!andcoAccount) return c.json({ error: "no_andco_account" }, 401);

  const { accessToken } = await auth.api.getAccessToken({
    body: { accountId: andcoAccount.accountId, userId: session.user.id },
    headers: c.req.raw.headers,
  });
  c.set("andco", andco.with(accessToken));
  await next();
});
```

```ts
app.get("/accounts", withAndco, async (c) => {
  const { data, error } = await c.var.andco.rest.http.GET("/accounts");
  if (error) return c.json({ error: error.code }, error.status ?? 500);
  return c.json(data);
});
```

## Sin Better Auth

El flujo OAuth manual, para cuando no quieres la dependencia.

```bash
npm install @andco/sdk hono
```

```ts title="src/andco.ts"
import { createAndcoInstanceForServer } from "@andco/sdk/server";

export const andco = createAndcoInstanceForServer({
  clientId: process.env.ANDCO_CLIENT_ID!,
  clientSecret: process.env.ANDCO_CLIENT_SECRET!,
});
```

Una sola instancia para todo el proceso. No guarda sesión: cada petición liga la suya.

### Iniciar la autorización

```ts title="src/routes/auth.ts"
import { Hono } from "hono";
import { getCookie, setCookie } from "hono/cookie";
import { andco } from "../andco.js";

export const auth = new Hono();

auth.get("/auth/andco", async (c) => {
  const { data: request, error } = await andco.oauth.createAuthorizationRequest({
    redirectTo: `${process.env.APP_ORIGIN}/auth/andco/callback`,
    scopes: ["openid", "email", "profile"],
  });
  if (error) return c.json({ error: error.code }, 500);

  setCookie(c, "andco_tx", JSON.stringify({ ...request, authorizationUrl: request.authorizationUrl.href }), {
    httpOnly: true,
    sameSite: "Lax",
    secure: true,
    maxAge: 600,
  });

  return c.redirect(request.authorizationUrl.href);
});
```

La cookie de transacción lleva el verificador PKCE. No puede viajar en la URL y no puede reconstruirse: sin ella el callback trae un código que nadie puede canjear.

### Recibir el callback

```ts
auth.get("/auth/andco/callback", async (c) => {
  const raw = getCookie(c, "andco_tx");
  if (!raw) return c.json({ error: "missing_transaction" }, 400);

  const stored = JSON.parse(raw);
  const { data: session, error } = await andco.oauth.exchangeCallback({
    callbackUrl: new URL(c.req.url),
    request: { ...stored, authorizationUrl: new URL(stored.authorizationUrl) },
  });
  setCookie(c, "andco_tx", "", { maxAge: 0 });
  if (error) return c.json({ error: error.code }, 400);

  await saveToYourSession(c, session);
  return c.redirect("/");
});
```

### Ligar la credencial a la petición

Un middleware que deja el cliente ligado en el contexto evita repetir `with()` en cada ruta.

```ts title="src/middleware/andco.ts"
import type { AndcoBoundClient } from "@andco/sdk";
import { createMiddleware } from "hono/factory";
import { andco } from "../andco.js";

export const withAndco = createMiddleware<{ Variables: { andco: AndcoBoundClient } }>(async (c, next) => {
  const session = await sessionFromYourStore(c);
  if (!session) return c.json({ error: "unauthenticated" }, 401);
  c.set("andco", andco.with(session));
  await next();
});
```

```ts
app.get("/accounts", withAndco, async (c) => {
  const { data, error } = await c.var.andco.rest.http.GET("/accounts");
  if (error) return c.json({ error: error.code }, error.status ?? 500);
  return c.json(data);
});
```

`with()` no muta la instancia compartida: devuelve un cliente nuevo. Por eso dos peticiones simultáneas no pueden mezclar credenciales.

### Refrescar

Si ligas la sesión completa —y no solo el token— sigue sin renovarse sola: `with()` es una consulta directa al token, sin persistencia ni renovación (igual que en el [flujo genérico del servidor](/docs/getting-started/server-authentication)). Renueva a mano y guarda el resultado, porque los refresh tokens rotan:

```ts
const { data: refreshed } = await andco.oauth.refresh(session.refreshToken!);
await saveToYourSession(c, refreshed);
```

## Webhooks

`@andco/protocol` trae los esquemas y el verificador de firma, así que tu manejador no reimplementa el formato:

```ts
import { ANDCO_WEBHOOK_HEADERS, andCoWebhookVerify } from "@andco/protocol";

app.post("/webhooks/andco", async (c) => {
  const body = await c.req.text();
  const trusted = await andCoWebhookVerify({
    body,
    signature: c.req.header(ANDCO_WEBHOOK_HEADERS.signature),
    eventId: c.req.header(ANDCO_WEBHOOK_HEADERS.eventId),
    timestamp: c.req.header(ANDCO_WEBHOOK_HEADERS.timestamp),
    secret: process.env.ANDCO_WEBHOOK_SECRET!,
  });
  if (!trusted) return c.json({ error: "invalid_signature" }, 400);

  await handleEvent(JSON.parse(body));
  return c.body(null, 204);
});
```

El cuerpo tiene que ser el texto crudo. Si lo parseas antes de verificar, lo que firmó Andco ya no existe: volver a serializarlo produce otros bytes y la comprobación falla.
