Skip to main content

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

Want to skip ahead?

Clone the complete example and run it locally.
1

Create a Hono app

npm create hono@latest unkey-hono
cd unkey-hono
Choose your preferred runtime (Node.js, Bun, Cloudflare Workers, etc.)
2

Install the Unkey middleware

npm install @unkey/hono
3

Add your root key

Create a .env file:
.env
UNKEY_ROOT_KEY="unkey_..."
The Hono middleware verifies keys directly against your root key.
4

Add the middleware

Update src/index.ts:
src/index.ts
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;
5

Run your app

npm run dev
6

Test it

Create a test key in your Unkey dashboard, then:
Test with valid key
curl http://localhost:3000 \
  -H "Authorization: Bearer YOUR_API_KEY"
You should see:
{
  "message": "Hello from protected route!",
  "keyId": "key_...",
  "valid": true
}
Without a key, you’ll get a 401:
Test without key
curl http://localhost:3000
{
  "error": "Unauthorized"
}

Protecting specific routes

Instead of protecting all routes, you can apply the middleware to specific paths:
src/index.ts
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:
FieldTypeDescription
validbooleanWhether the key passed all checks
codestringStatus code (VALID, NOT_FOUND, RATE_LIMITED, etc.)
keyIdstringThe key’s unique identifier
namestring?Human-readable name of the key
metaobject?Custom metadata associated with the key
expiresnumber?Unix timestamp (in milliseconds) when the key will expire. (if set)
creditsnumber?Remaining uses (if usage limits set)
enabledbooleanWhether the key is enabled
rolesstring[]?Named role(s) assigned to the key, each representing a set of permissions
permissionsstring[]?List of individual permissions granted to the key
identityobject?Identity info if externalId was set when creating the key
ratelimitsobject[]?Rate limit states (if rate limiting configured)

Middleware options

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

Troubleshooting

  • Ensure the key hasn’t expired or been revoked
  • Verify the header format: Authorization: Bearer YOUR_KEY
  • 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
Use wrangler secrets for your root key:
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.
Last modified on February 6, 2026