Skip to content

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 /api
app.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

FunctionReturn TypeDescription
getAuth(c)ValidatedToken | undefinedGet the validated token from Hono context
getAuthTokenType(c)'Bearer' | 'DPoP' | undefinedGet the token type from Hono context

Cloudflare Workers Example

Hono is commonly deployed on Cloudflare Workers. Here is a complete example:

src/index.ts
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;
wrangler.toml
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 routes
app.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

PropertyTypeDescription
ctx.state.authValidatedToken | undefinedValidated token claims
ctx.state.authTokenType'Bearer' | 'DPoP' | undefinedToken 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 auth
router.get('/health', (ctx) => {
ctx.body = { status: 'ok' };
});
// Optional auth
router.get('/api/posts', authrimOptionalMiddleware(server), (ctx) => {
const user = ctx.state.auth?.sub ?? 'anonymous';
ctx.body = { user, posts: getPosts(user) };
});
// Protected routes
const 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

FunctionReturn TypeDescription
getAuthFromRequest(context)ValidatedToken | undefinedExtract auth from ExecutionContext or Request
getAuthTokenTypeFromRequest(context)'Bearer' | 'DPoP' | undefinedExtract 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

auth.module.ts
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 {}
users.controller.ts
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

AspectExpressFastifyHonoKoaNestJS
Auth locationreq.authrequest.authgetAuth(c)ctx.state.authgetAuthFromRequest(req)
Required middlewareauthrimMiddleware()authrimPreHandler()authrimMiddleware()authrimMiddleware()createAuthrimGuard()
Optional middlewareauthrimOptionalMiddleware()authrimOptionalPreHandler()authrimOptionalMiddleware()authrimOptionalMiddleware()createAuthrimOptionalGuard()
App-wide supportapp.use(...)authrimPlugin()app.use(path, ...)app.use(...)@UseGuards() on controller
Type safetydeclare globaldeclare moduleContext variablesdeclare moduleParam decorators

Next Steps