Hono, Koa & NestJS Adapters
Overview
Section titled “Overview”@authrim/server provides adapters for Hono, Koa, and NestJS. Each adapter validates access tokens (Bearer and DPoP) and exposes the authenticated user context through the framework’s idiomatic patterns.
For Express and Fastify, see Express & Fastify Adapters.
Hono Adapter
Section titled “Hono Adapter”Import from @authrim/server/adapters/hono.
Hono uses context variables (not direct request properties) for authentication data. Use the getAuth() and getAuthTokenType() helper functions to access them.
authrimMiddleware()
Section titled “authrimMiddleware()”Required authentication — returns 401 for unauthenticated requests:
import { Hono } from 'hono';import { createAuthrimServer } from '@authrim/server';import { authrimMiddleware, getAuth, getAuthTokenType,} from '@authrim/server/adapters/hono';
const server = createAuthrimServer({ issuer: 'https://auth.example.com', audience: 'https://api.example.com',});
const app = new Hono();
// Protect all routes under /apiapp.use('/api/*', authrimMiddleware(server));
app.get('/api/profile', (c) => { const auth = getAuth(c); // ValidatedToken (guaranteed) const type = getAuthTokenType(c); // 'Bearer' | 'DPoP'
return c.json({ sub: auth?.claims.sub, tokenType: type });});authrimOptionalMiddleware()
Section titled “authrimOptionalMiddleware()”Optional authentication — continues for unauthenticated requests:
import { authrimOptionalMiddleware, getAuth,} from '@authrim/server/adapters/hono';
app.get('/api/feed', authrimOptionalMiddleware(server), (c) => { const auth = getAuth(c);
if (auth) { return c.json({ feed: getPersonalizedFeed(auth.claims.sub) }); } return c.json({ feed: getPublicFeed() });});Helper Functions
Section titled “Helper Functions”| Function | Return Type | Description |
|---|---|---|
getAuth(c) | ValidatedToken | undefined | Get the validated token from Hono context |
getAuthTokenType(c) | 'Bearer' | 'DPoP' | undefined | Get the token type from Hono context |
Cloudflare Workers Example
Section titled “Cloudflare Workers Example”Hono is commonly deployed on Cloudflare Workers. Here is a complete example:
import { Hono } from 'hono';import { createAuthrimServer } from '@authrim/server';import { authrimMiddleware, authrimOptionalMiddleware, getAuth,} from '@authrim/server/adapters/hono';
type Bindings = { AUTHRIM_ISSUER: string; AUTHRIM_AUDIENCE: string;};
const app = new Hono<{ Bindings: Bindings }>();
app.use('/api/*', async (c, next) => { const server = createAuthrimServer({ issuer: c.env.AUTHRIM_ISSUER, audience: c.env.AUTHRIM_AUDIENCE, });
return authrimMiddleware(server)(c, next);});
app.get('/api/profile', (c) => { const auth = getAuth(c); return c.json({ sub: auth!.claims.sub });});
app.get('/health', (c) => c.json({ status: 'ok' }));
export default app;name = "my-api"main = "src/index.ts"compatibility_date = "2024-12-01"
[vars]AUTHRIM_ISSUER = "https://auth.example.com"AUTHRIM_AUDIENCE = "https://api.example.com"Scope Validation in Hono
Section titled “Scope Validation in Hono”import { createMiddleware } from 'hono/factory';
function requireScope(...scopes: string[]) { return createMiddleware(async (c, next) => { const auth = getAuth(c);
if (!auth) { return c.json({ error: 'unauthorized' }, 401); }
const tokenScopes = auth.claims.scope?.split(' ') ?? []; const hasAll = scopes.every((s) => tokenScopes.includes(s));
if (!hasAll) { return c.json({ error: 'insufficient_scope', required: scopes, }, 403); }
await next(); });}
app.get('/api/admin', authrimMiddleware(server), requireScope('admin:read'), (c) => { return c.json({ admin: true });});Koa Adapter
Section titled “Koa Adapter”Import from @authrim/server/adapters/koa.
Koa stores authentication data on ctx.state.
authrimMiddleware()
Section titled “authrimMiddleware()”Required authentication:
import Koa from 'koa';import Router from '@koa/router';import { createAuthrimServer } from '@authrim/server';import { authrimMiddleware } from '@authrim/server/adapters/koa';
const server = createAuthrimServer({ issuer: 'https://auth.example.com', audience: 'https://api.example.com',});
const app = new Koa();const router = new Router();
// Protect all API routesapp.use(authrimMiddleware(server));
router.get('/api/profile', (ctx) => { ctx.body = { sub: ctx.state.auth.claims.sub, tokenType: ctx.state.authTokenType, };});
app.use(router.routes());app.listen(3000);authrimOptionalMiddleware()
Section titled “authrimOptionalMiddleware()”Optional authentication:
import { authrimOptionalMiddleware } from '@authrim/server/adapters/koa';
router.get('/api/feed', authrimOptionalMiddleware(server), (ctx) => { if (ctx.state.auth) { ctx.body = { feed: getPersonalizedFeed(ctx.state.auth.claims.sub) }; } else { ctx.body = { feed: getPublicFeed() }; }});State Properties
Section titled “State Properties”| Property | Type | Description |
|---|---|---|
ctx.state.auth | ValidatedToken | undefined | Validated token result, including claims, issuer, tenantId, tokenType, and expiresIn |
ctx.state.authTokenType | 'Bearer' | 'DPoP' | undefined | Token type used |
Complete Koa Example
Section titled “Complete Koa Example”import Koa from 'koa';import Router from '@koa/router';import { createAuthrimServer } from '@authrim/server';import { authrimMiddleware, authrimOptionalMiddleware,} from '@authrim/server/adapters/koa';
const server = createAuthrimServer({ issuer: 'https://auth.example.com', audience: 'https://api.example.com',});
const app = new Koa();const router = new Router();
// Health check — no authrouter.get('/health', (ctx) => { ctx.body = { status: 'ok' };});
// Optional authrouter.get('/api/posts', authrimOptionalMiddleware(server), (ctx) => { const user = ctx.state.auth?.claims.sub ?? 'anonymous'; ctx.body = { user, posts: getPosts(user) };});
// Protected routesconst protectedRouter = new Router({ prefix: '/api/me' });protectedRouter.use(authrimMiddleware(server));
protectedRouter.get('/profile', (ctx) => { ctx.body = { sub: ctx.state.auth.claims.sub, email: ctx.state.auth.claims.email, };});
protectedRouter.get('/settings', (ctx) => { ctx.body = { settings: getUserSettings(ctx.state.auth.claims.sub) };});
app.use(router.routes());app.use(protectedRouter.routes());app.listen(3000);TypeScript Type Extension for Koa
Section titled “TypeScript Type Extension for Koa”import type { ValidatedToken } from '@authrim/server';
declare module 'koa' { interface DefaultState { auth?: ValidatedToken; authTokenType?: 'Bearer' | 'DPoP'; }}NestJS Adapter
Section titled “NestJS Adapter”Import from @authrim/server/adapters/nestjs.
NestJS uses Guards for route protection. The adapter provides factory functions that create Guard classes compatible with NestJS’s dependency injection system.
createAuthrimGuard()
Section titled “createAuthrimGuard()”Required authentication — throws HttpException(401) on invalid tokens:
import { Module, Controller, Get, Req, UseGuards } from '@nestjs/common';import { HttpException } from '@nestjs/common';import { createAuthrimServer } from '@authrim/server';import { createAuthrimGuard,} from '@authrim/server/adapters/nestjs';import type { ValidatedToken } from '@authrim/server';
const server = createAuthrimServer({ issuer: 'https://auth.example.com', audience: 'https://api.example.com',});
const AuthGuard = createAuthrimGuard(server, HttpException);
@Controller('api')@UseGuards(AuthGuard)export class ApiController { @Get('profile') getProfile(@Req() req: Request & { auth?: ValidatedToken; authTokenType?: 'Bearer' | 'DPoP'; }) { const auth = req.auth; const tokenType = req.authTokenType;
return { sub: auth?.claims.sub, tokenType }; }}createAuthrimOptionalGuard()
Section titled “createAuthrimOptionalGuard()”Optional authentication — does not reject unauthenticated requests:
import { createAuthrimOptionalGuard } from '@authrim/server/adapters/nestjs';
const OptionalAuthGuard = createAuthrimOptionalGuard(server);
@Controller('public')@UseGuards(OptionalAuthGuard)export class PublicController { @Get('feed') getFeed(@Req() req: Request & { auth?: ValidatedToken }) { const auth = req.auth; if (auth) { return { feed: getPersonalizedFeed(auth.claims.sub) }; } return { feed: getPublicFeed() }; }}Helper Functions
Section titled “Helper Functions”| Function | Return Type | Description |
|---|---|---|
getAuthFromRequest(context) | ValidatedToken | undefined | Extract auth from a NestJS ExecutionContext |
getAuthTokenTypeFromRequest(context) | 'Bearer' | 'DPoP' | undefined | Extract token type |
Custom @Auth() Parameter Decorator
Section titled “Custom @Auth() Parameter Decorator”Create a convenient parameter decorator for cleaner controller code:
import { createParamDecorator, ExecutionContext } from '@nestjs/common';import { getAuthFromRequest } from '@authrim/server/adapters/nestjs';import type { ValidatedToken } from '@authrim/server';
export const Auth = createParamDecorator( (data: unknown, ctx: ExecutionContext): ValidatedToken | undefined => { return getAuthFromRequest(ctx); },);Use it in controllers:
@Controller('api')@UseGuards(AuthGuard)export class ApiController { @Get('profile') getProfile(@Auth() auth: ValidatedToken) { return { sub: auth.claims.sub, email: auth.claims.email }; }}Complete NestJS Example
Section titled “Complete NestJS Example”import { Module, Global } from '@nestjs/common';import { HttpException } from '@nestjs/common';import { createAuthrimServer } from '@authrim/server';import { createAuthrimGuard, createAuthrimOptionalGuard } from '@authrim/server/adapters/nestjs';
const server = createAuthrimServer({ issuer: process.env.AUTHRIM_ISSUER!, audience: process.env.AUTHRIM_AUDIENCE!,});
export const AuthGuard = createAuthrimGuard(server, HttpException, { realm: 'my-api', onError: (err) => console.warn('Auth error:', err.code),});
export const OptionalAuthGuard = createAuthrimOptionalGuard(server);
@Global()@Module({})export class AuthModule {}import { Controller, Get, UseGuards } from '@nestjs/common';import { AuthGuard, OptionalAuthGuard } from './auth.module';import { Auth } from './auth.decorator';import type { ValidatedToken } from '@authrim/server';
@Controller('users')export class UsersController { @Get('me') @UseGuards(AuthGuard) getMe(@Auth() auth: ValidatedToken) { return { sub: auth.claims.sub, email: auth.claims.email }; }
@Get() @UseGuards(OptionalAuthGuard) listUsers(@Auth() auth: ValidatedToken | undefined) { if (auth) { return { users: getAllUsers(), requestedBy: auth.claims.sub }; } return { users: getPublicUsers() }; }}Framework Comparison
Section titled “Framework Comparison”| Aspect | Express | Fastify | Hono | Koa | NestJS |
|---|---|---|---|---|---|
| Auth location | req.auth | request.auth | getAuth(c) | ctx.state.auth | req.auth or getAuthFromRequest(ctx) |
| Required middleware | authrimMiddleware() | authrimPreHandler() | authrimMiddleware() | authrimMiddleware() | createAuthrimGuard() |
| Optional middleware | authrimOptionalMiddleware() | authrimOptionalPreHandler() | authrimOptionalMiddleware() | authrimOptionalMiddleware() | createAuthrimOptionalGuard() |
| App-wide support | app.use(...) | authrimPlugin() | app.use(path, ...) | app.use(...) | @UseGuards() on controller |
| Type safety | declare global | declare module | Context variables | declare module | Param decorators |
Next Steps
Section titled “Next Steps”- Express & Fastify Adapters — Express and Fastify integration details
- Error Handling — Error codes and response utilities
- Configuration Reference — Server configuration options