> ## Documentation Index
> Fetch the complete documentation index at: https://unkey.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Hono

> Add API key authentication to your Hono application using Unkey middleware. Protect routes with automatic key verification on requests.

## What you'll build

A Hono app with API key authentication using the `@unkey/hono` middleware. All routes (or specific ones) require a valid API key.

**Time to complete:** \~5 minutes

## Prerequisites

* [Unkey account](https://app.unkey.com/auth/sign-up) (free)
* [Keyspace created](https://app.unkey.com/apis) in your Unkey dashboard
* Node.js 18+ or Bun

<Card title="Want to skip ahead?" icon="github" href="https://github.com/unkeyed/examples/tree/main/hono">
  Clone the complete example and run it locally.
</Card>

<Steps titleSize="h3">
  <Step title="Create a Hono app">
    <CodeGroup>
      ```bash npm theme={"theme":"kanagawa-wave"}
      npm create hono@latest unkey-hono
      cd unkey-hono
      ```

      ```bash pnpm theme={"theme":"kanagawa-wave"}
      pnpm create hono@latest unkey-hono
      cd unkey-hono
      ```

      ```bash bun theme={"theme":"kanagawa-wave"}
      bun create hono@latest unkey-hono
      cd unkey-hono
      ```
    </CodeGroup>

    Choose your preferred runtime (Node.js, Bun, Cloudflare Workers, etc.)
  </Step>

  <Step title="Install the Unkey middleware">
    <CodeGroup>
      ```bash npm theme={"theme":"kanagawa-wave"}
      npm install @unkey/hono
      ```

      ```bash pnpm theme={"theme":"kanagawa-wave"}
      pnpm add @unkey/hono
      ```

      ```bash bun theme={"theme":"kanagawa-wave"}
      bun add @unkey/hono
      ```
    </CodeGroup>
  </Step>

  <Step title="Add your root key">
    Create a `.env` file:

    ```bash .env theme={"theme":"kanagawa-wave"}
    UNKEY_ROOT_KEY="unkey_..."
    ```

    <Note>The Hono middleware verifies keys directly against your root key.</Note>
  </Step>

  <Step title="Add the middleware">
    Update `src/index.ts`:

    ```ts src/index.ts theme={"theme":"kanagawa-wave"}
    import { Hono } from "hono";
    import { unkey, UnkeyContext } from "@unkey/hono";

    // Type the context so you get autocomplete for c.get("unkey")
    const app = new Hono<{ Variables: { unkey: UnkeyContext } }>();

    // Protect all routes with API key authentication
    app.use(
      "*",
      unkey({
        rootKey: process.env.UNKEY_ROOT_KEY!,
      }),
    );

    // This route now requires a valid API key
    app.get("/", (c) => {
      // Access verification result from context
      const keyInfo = c.get("unkey");

      return c.json({
        message: "Hello from protected route!",
        keyId: keyInfo.keyId,
        valid: keyInfo.valid,
      });
    });

    // Another protected route
    app.get("/secret", (c) => {
      const keyInfo = c.get("unkey");

      return c.json({
        secret: "data",
        identity: keyInfo.identity,
        meta: keyInfo.meta,
      });
    });

    export default app;
    ```
  </Step>

  <Step title="Run your app">
    <CodeGroup>
      ```bash npm theme={"theme":"kanagawa-wave"}
      npm run dev
      ```

      ```bash pnpm theme={"theme":"kanagawa-wave"}
      pnpm dev
      ```

      ```bash bun theme={"theme":"kanagawa-wave"}
      bun run dev
      ```
    </CodeGroup>
  </Step>

  <Step title="Test it">
    Create a test key in your [Unkey dashboard](https://app.unkey.com), then:

    ```bash Test with valid key theme={"theme":"kanagawa-wave"}
    curl http://localhost:3000 \
      -H "Authorization: Bearer YOUR_API_KEY"
    ```

    You should see:

    ```json theme={"theme":"kanagawa-wave"}
    {
      "message": "Hello from protected route!",
      "keyId": "key_...",
      "valid": true
    }
    ```

    Without a key, you'll get a 401:

    ```bash Test without key theme={"theme":"kanagawa-wave"}
    curl http://localhost:3000
    ```

    ```json theme={"theme":"kanagawa-wave"}
    {
      "error": "Unauthorized"
    }
    ```
  </Step>
</Steps>

## Protecting specific routes

Instead of protecting all routes, you can apply the middleware to specific paths:

```ts src/index.ts theme={"theme":"kanagawa-wave"}
import { Hono } from "hono";
import { unkey, UnkeyContext } from "@unkey/hono";

const app = new Hono<{ Variables: { unkey: UnkeyContext } }>();

// Public route, no middleware
app.get("/", (c) => {
  return c.json({ message: "Welcome! This route is public." });
});

// Protected routes, apply middleware to /api/* paths only
app.use("/api/*", unkey({ rootKey: process.env.UNKEY_ROOT_KEY! }));

app.get("/api/secret", (c) => {
  const keyInfo = c.get("unkey");
  return c.json({ secret: "data", keyId: keyInfo.keyId });
});

app.get("/api/user", (c) => {
  const keyInfo = c.get("unkey");
  return c.json({ identity: keyInfo.identity });
});

export default app;
```

## What's in the context?

After verification, `c.get("unkey")` contains:

| Field         | Type        | Description                                                               |
| ------------- | ----------- | ------------------------------------------------------------------------- |
| `valid`       | `boolean`   | Whether the key passed all checks                                         |
| `code`        | `string`    | Status code (`VALID`, `NOT_FOUND`, `RATE_LIMITED`, etc.)                  |
| `keyId`       | `string`    | The key's unique identifier                                               |
| `name`        | `string?`   | Human-readable name of the key                                            |
| `meta`        | `object?`   | Custom metadata associated with the key                                   |
| `expires`     | `number?`   | Unix timestamp (in milliseconds) when the key will expire. (if set)       |
| `credits`     | `number?`   | Remaining uses (if usage limits set)                                      |
| `enabled`     | `boolean`   | Whether the key is enabled                                                |
| `roles`       | `string[]?` | Named role(s) assigned to the key, each representing a set of permissions |
| `permissions` | `string[]?` | List of individual permissions granted to the key                         |
| `identity`    | `object?`   | Identity info if `externalId` was set when creating the key               |
| `ratelimits`  | `object[]?` | Rate limit states (if rate limiting configured)                           |

## Middleware options

```ts theme={"theme":"kanagawa-wave"}
unkey({
  rootKey: process.env.UNKEY_ROOT_KEY!, // Required: Your root key

  // Optional: Custom error handling
  onError: (c, error) => {
    console.error("Unkey error:", error);
    return c.json({ error: "Auth service unavailable" }, 503);
  },

  // Optional: Custom unauthorized response
  handleInvalidKey: (c, result) => {
    return c.json(
      {
        error: "Invalid API key",
        code: result.code,
      },
      401,
    );
  },
});
```

## Next steps

<CardGroup cols={2}>
  <Card title="Add rate limiting" icon="gauge-high" href="/platform/apis/features/ratelimiting/overview">
    Limit requests per key
  </Card>

  <Card title="Set usage limits" icon="calculator" href="/platform/apis/features/remaining">
    Cap total requests per key
  </Card>

  <Card title="Add permissions" icon="user-shield" href="/platform/apis/features/authorization/introduction">
    Fine-grained access control
  </Card>

  <Card title="Hono SDK Reference" icon="book" href="/libraries/ts/hono">
    Full middleware documentation
  </Card>
</CardGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Getting 401 even with a valid key?">
    * Ensure the key hasn't expired or been revoked - Verify the header format:
      `Authorization: Bearer YOUR_KEY`
  </Accordion>

  <Accordion title="Environment variables not loading?">
    * For Node.js: Install `dotenv` and add `import 'dotenv/config'` at the top -
      For Bun: `.env` is loaded automatically - For Cloudflare Workers: Use
      `wrangler secret` or `wrangler.toml`
  </Accordion>

  <Accordion title="Deploying to Cloudflare Workers?">
    Use wrangler secrets for your root key:

    ```bash theme={"theme":"kanagawa-wave"}
    npx wrangler secret put UNKEY_ROOT_KEY
    ```

    Then access it from your Hono bindings. Add `UNKEY_ROOT_KEY` to your `Bindings` type and read it via `ctx.env.UNKEY_ROOT_KEY` or `env.UNKEY_ROOT_KEY` in your handlers.
  </Accordion>
</AccordionGroup>
