Hono, Koa & NestJS Adapters
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
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()
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.sub, tokenType: type });});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.sub) }); } return c.json({ feed: getPublicFeed() });});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
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!.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
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.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
Import from @authrim/server/adapters/koa.
Koa stores authentication data on ctx.state.
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.sub, tokenType: ctx.state.authTokenType, };});
app.use(router.routes());app.listen(3000);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.sub) }; } else { ctx.body = { feed: getPublicFeed() }; }});State Properties
| Property | Type | Description |
|---|---|---|
ctx.state.auth | ValidatedToken | undefined | Validated token claims |
ctx.state.authTokenType | 'Bearer' | 'DPoP' | undefined | Token type used |
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?.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.sub, email: ctx.state.auth.email };});
protectedRouter.get('/settings', (ctx) => { ctx.body = { settings: getUserSettings(ctx.state.auth.sub) };});
app.use(router.routes());app.use(protectedRouter.routes());app.listen(3000);TypeScript Type Extension for Koa
import type { ValidatedToken } from '@authrim/server';
declare module 'koa' { interface DefaultState { auth: ValidatedToken; authTokenType: 'Bearer' | 'DPoP'; }}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()
Required authentication — throws HttpException(401) on invalid tokens:
import { Module, Controller, Get, UseGuards } from '@nestjs/common';import { HttpException } from '@nestjs/common';import { createAuthrimServer } from '@authrim/server';import { createAuthrimGuard, getAuthFromRequest, getAuthTokenTypeFromRequest,} from '@authrim/server/adapters/nestjs';
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) { const auth = getAuthFromRequest(req); const tokenType = getAuthTokenTypeFromRequest(req);
return { sub: auth?.sub, tokenType }; }}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) { const auth = getAuthFromRequest(req); if (auth) { return { feed: getPersonalizedFeed(auth.sub) }; } return { feed: getPublicFeed() }; }}Helper Functions
| Function | Return Type | Description |
|---|---|---|
getAuthFromRequest(context) | ValidatedToken | undefined | Extract auth from ExecutionContext or Request |
getAuthTokenTypeFromRequest(context) | 'Bearer' | 'DPoP' | undefined | Extract token type |
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 => { const request = ctx.switchToHttp().getRequest(); return getAuthFromRequest(request); },);Use it in controllers:
@Controller('api')@UseGuards(AuthGuard)export class ApiController { @Get('profile') getProfile(@Auth() auth: ValidatedToken) { return { sub: auth.sub, email: auth.email }; }}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.sub, email: auth.email }; }
@Get() @UseGuards(OptionalAuthGuard) listUsers(@Auth() auth: ValidatedToken | undefined) { if (auth) { return { users: getAllUsers(), requestedBy: auth.sub }; } return { users: getPublicUsers() }; }}Framework Comparison
| Aspect | Express | Fastify | Hono | Koa | NestJS |
|---|---|---|---|---|---|
| Auth location | req.auth | request.auth | getAuth(c) | ctx.state.auth | getAuthFromRequest(req) |
| 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
- Express & Fastify Adapters — Express and Fastify integration details
- Error Handling — Error codes and response utilities
- Configuration Reference — Server configuration options