Install
Copy
Ask AI
npm install @unkey/api express
Basic Middleware
middleware/auth.ts
Copy
Ask AI
import { Unkey } from "@unkey/api";
import { Request, Response, NextFunction } from "express";
const unkey = new Unkey({ rootKey: process.env.UNKEY_ROOT_KEY! });
// Extend Express Request type
declare global {
namespace Express {
interface Request {
unkey?: V2KeysVerifyKeyResponseData;
}
}
}
export async function unkeyAuth(
req: Request,
res: Response,
next: NextFunction
) {
// 1. Extract API key
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith("Bearer ")) {
return res.status(401).json({ error: "Missing API key" });
}
const apiKey = authHeader.slice(7);
// 2. Verify with Unkey
const { meta, data, error } = await unkey.keys.verifyKey({
key: apiKey,
});
// 3. Handle errors
if (error) {
console.error("Unkey error:", error);
return res.status(503).json({ error: "Authentication service unavailable" });
}
// 4. Check validity
if (!data.valid) {
const status = data.code === "RATE_LIMITED" ? 429 : 401;
return res.status(status).json({ error: data.code });
}
// 5. Attach to request and continue
req.unkey = data;
next();
}
Use the Middleware
app.ts
Copy
Ask AI
import express from "express";
import { unkeyAuth } from "./middleware/auth";
const app = express();
// Public routes
app.get("/health", (req, res) => {
res.json({ status: "ok" });
});
// Protected routes
app.get("/api/data", unkeyAuth, (req, res) => {
// Access verification result
const { identity, meta, remaining } = req.unkey!;
res.json({
message: "Access granted",
user: identity?.externalId,
plan: meta?.plan,
creditsRemaining: remaining,
});
});
// Protect entire router
const apiRouter = express.Router();
apiRouter.use(unkeyAuth);
apiRouter.get("/users", (req, res) => {
res.json({ users: [] });
});
apiRouter.post("/users", (req, res) => {
res.json({ created: true });
});
app.use("/api/v1", apiRouter);
app.listen(3000);
With Rate Limit Headers
Include rate limit info in response headers:middleware/auth.ts
Copy
Ask AI
export async function unkeyAuth(
req: Request,
res: Response,
next: NextFunction
) {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith("Bearer ")) {
return res.status(401).json({ error: "Missing API key" });
}
const { meta, data, error } = await unkey.keys.verifyKey({
key: authHeader.slice(7),
});
if (error) {
return res.status(503).json({ error: "Service unavailable" });
}
// Add rate limit headers if available (v2 uses ratelimits array)
if (data.ratelimits?.[0]) {
const rl = data.ratelimits[0];
res.set({
"X-RateLimit-Limit": rl.limit.toString(),
"X-RateLimit-Remaining": rl.remaining.toString(),
"X-RateLimit-Reset": rl.reset.toString(),
});
}
// Add remaining credits header if available
if (data.credits !== undefined) {
res.set("X-Credits-Remaining", data.credits.toString());
}
if (!data.valid) {
const status = data.code === "RATE_LIMITED" ? 429 : 401;
return res.status(status).json({ error: data.code });
}
req.unkey = data;
next();
}
Permission-Based Access
Create middleware that requires specific permissions:middleware/permissions.ts
Copy
Ask AI
import { Unkey } from "@unkey/api";
import { Request, Response, NextFunction } from "express";
const unkey = new Unkey({ rootKey: process.env.UNKEY_ROOT_KEY! });
export function requirePermission(permission: string) {
return async (req: Request, res: Response, next: NextFunction) => {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith("Bearer ")) {
return res.status(401).json({ error: "Missing API key" });
}
const { meta, data, error } = await unkey.keys.verifyKey({
key: authHeader.slice(7),
permissions: permission, // Check for this permission
});
if (error) {
return res.status(503).json({ error: "Service unavailable" });
}
if (!data.valid) {
if (data.code === "INSUFFICIENT_PERMISSIONS") {
return res.status(403).json({
error: "Forbidden",
required: permission,
});
}
return res.status(401).json({ error: data.code });
}
req.unkey = data;
next();
};
}
Copy
Ask AI
// Anyone with a valid key
app.get("/api/data", unkeyAuth, handler);
// Only keys with "admin" permission
app.delete("/api/users/:id", requirePermission("admin"), deleteUser);
// Only keys with "billing.read" permission
app.get("/api/invoices", requirePermission("billing.read"), getInvoices);
Configurable Middleware Factory
Create a flexible middleware that can be configured per-route:middleware/auth.ts
Copy
Ask AI
import { Unkey } from "@unkey/api";
import { Request, Response, NextFunction } from "express";
const unkey = new Unkey({ rootKey: process.env.UNKEY_ROOT_KEY! });
interface AuthOptions {
permissions?: string;
getKey?: (req: Request) => string | null;
onError?: (req: Request, res: Response, error: Error) => void;
onInvalid?: (req: Request, res: Response, result: V2KeysVerifyKeyResponseData) => void;
}
export function createAuthMiddleware(options: AuthOptions = {}) {
const {
permissions,
getKey = (req) => req.headers.authorization?.slice(7) ?? null,
onError = (req, res) => res.status(503).json({ error: "Service unavailable" }),
onInvalid = (req, res, result) => res.status(401).json({ error: result.code }),
} = options;
return async (req: Request, res: Response, next: NextFunction) => {
const apiKey = getKey(req);
if (!apiKey) {
return res.status(401).json({ error: "Missing API key" });
}
const { meta, data, error } = await unkey.keys.verifyKey({
key: apiKey,
permissions,
});
if (error) {
return onError(req, res, error);
}
if (!data.valid) {
return onInvalid(req, res, data);
}
req.unkey = data;
next();
};
}
// Pre-configured middlewares
export const unkeyAuth = createAuthMiddleware();
export const adminAuth = createAuthMiddleware({
permissions: "admin",
onInvalid: (req, res, result) => {
if (result.code === "INSUFFICIENT_PERMISSIONS") {
return res.status(403).json({ error: "Admin access required" });
}
return res.status(401).json({ error: result.code });
},
});
Error Handling
Graceful degradation when Unkey is unavailable:middleware/auth.ts
Copy
Ask AI
export async function unkeyAuth(
req: Request,
res: Response,
next: NextFunction
) {
const apiKey = req.headers.authorization?.slice(7);
if (!apiKey) {
return res.status(401).json({ error: "Missing API key" });
}
try {
const { meta, data, error } = await unkey.keys.verifyKey({
key: apiKey,
});
if (error) {
// Log for monitoring
console.error("Unkey verification error:", error);
// Option 1: Fail closed (more secure)
return res.status(503).json({ error: "Authentication unavailable" });
// Option 2: Fail open (better availability, less secure)
// console.warn("Allowing request due to Unkey unavailability");
// return next();
}
if (!data.valid) {
return res.status(401).json({ error: data.code });
}
req.unkey = data;
next();
} catch (e) {
console.error("Unexpected auth error:", e);
return res.status(503).json({ error: "Authentication service error" });
}
}
TypeScript Setup
types/express.d.ts
Copy
Ask AI
import { V2KeysVerifyKeyResponseData } from "@unkey/api/models/components";
declare global {
namespace Express {
interface Request {
unkey?: V2KeysVerifyKeyResponseData;
}
}
}
export {};
tsconfig.json:
Copy
Ask AI
{
"compilerOptions": {
"typeRoots": ["./node_modules/@types", "./types"]
}
}

