Express & Fastifyアダプター
概要
@authrim/server は Express と Fastify 向けのファーストクラスアダプターを提供します。両アダプターは受信するアクセストークン(BearerおよびDPoP)を検証し、リクエストオブジェクトに認証コンテキストを設定し、適切な WWW-Authenticate エラーヘッダーを返します。
共通: MiddlewareOptions
両アダプターは共通の MiddlewareOptions 設定を受け付けます:
import type { MiddlewareOptions } from '@authrim/server';
const options: MiddlewareOptions = { /** Realm value for WWW-Authenticate header */ realm: 'my-api',
/** Custom error handler — called when token validation fails */ onError: (error) => { console.error('Auth error:', error.code, error.message); },};| オプション | 型 | デフォルト | 説明 |
|---|---|---|---|
realm | string | undefined | WWW-Authenticate レスポンスヘッダーに含まれるRealm値 |
onError | (error: AuthrimServerError) => void | undefined | 検証失敗時に呼び出されるコールバック(ロギング、メトリクス用) |
Expressアダプター
@authrim/server/adapters/express からインポートします。
authrimMiddleware()
必須認証 — 有効なトークンのないリクエストを拒否します(401を返します):
import express from 'express';import { createAuthrimServer } from '@authrim/server';import { authrimMiddleware } from '@authrim/server/adapters/express';
const server = createAuthrimServer({ issuer: 'https://auth.example.com', audience: 'https://api.example.com',});
const app = express();
// Protect all routes under /apiapp.use('/api', authrimMiddleware(server));
app.get('/api/profile', (req, res) => { // req.auth is guaranteed to be set (ValidatedToken) res.json({ sub: req.auth.sub, email: req.auth.email, tokenType: req.authTokenType, // 'Bearer' | 'DPoP' });});authrimOptionalMiddleware()
オプション認証 — トークンがなくても処理を続行します。未認証のリクエストでは req.auth が undefined になります:
import { authrimOptionalMiddleware } from '@authrim/server/adapters/express';
app.get( '/api/posts', authrimOptionalMiddleware(server), (req, res) => { if (req.auth) { // Authenticated — show personalized content res.json({ posts: getPostsForUser(req.auth.sub), user: req.auth.sub }); } else { // Anonymous — show public content res.json({ posts: getPublicPosts() }); } },);リクエストプロパティ
ミドルウェア実行後、req で以下のプロパティが利用可能です:
| プロパティ | 型 | 説明 |
|---|---|---|
req.auth | ValidatedToken | undefined | 検証済みトークンのクレーム(sub、iss、aud、expなど) |
req.authTokenType | 'Bearer' | 'DPoP' | undefined | 認証に使用されたトークンタイプ |
スコープベースのアクセス制御
function requireScope(...scopes: string[]) { return (req: express.Request, res: express.Response, next: express.NextFunction) => { const tokenScopes = req.auth?.scope?.split(' ') ?? []; const hasAll = scopes.every((s) => tokenScopes.includes(s));
if (!hasAll) { return res.status(403).json({ error: 'insufficient_scope', error_description: `Required scopes: ${scopes.join(' ')}`, }); } next(); };}
app.get( '/api/admin/users', authrimMiddleware(server), requireScope('admin:read'), (req, res) => { res.json({ users: getAllUsers() }); },);Express完全サンプル
import express from 'express';import { createAuthrimServer } from '@authrim/server';import { authrimMiddleware, authrimOptionalMiddleware,} from '@authrim/server/adapters/express';
const server = createAuthrimServer({ issuer: 'https://auth.example.com', audience: 'https://api.example.com',});
const app = express();app.use(express.json());
// Public endpoint — no authapp.get('/health', (req, res) => { res.json({ status: 'ok' });});
// Optional auth — enhanced for logged-in usersapp.get('/api/feed', authrimOptionalMiddleware(server), (req, res) => { if (req.auth) { res.json({ feed: getPersonalizedFeed(req.auth.sub) }); } else { res.json({ feed: getPublicFeed() }); }});
// Required auth — protected routesapp.use('/api/me', authrimMiddleware(server));
app.get('/api/me/profile', (req, res) => { res.json({ sub: req.auth.sub, email: req.auth.email });});
app.listen(3000, () => console.log('Server running on :3000'));TypeScript型拡張
req.auth と req.authTokenType に適切な型を追加するには、Expressの名前空間を拡張します:
import type { ValidatedToken } from '@authrim/server';
declare global { namespace Express { interface Request { auth: ValidatedToken; authTokenType: 'Bearer' | 'DPoP'; } }}Fastifyアダプター
@authrim/server/adapters/fastify からインポートします。
Fastifyは ルートごとのハンドラー と アプリ全体のプラグイン の2つの統合スタイルを提供します。
ルートごと: authrimPreHandler / authrimOptionalPreHandler
Fastifyの preHandler フックを使用して、特定のルートに認証を適用します:
import Fastify from 'fastify';import { createAuthrimServer } from '@authrim/server';import { authrimPreHandler, authrimOptionalPreHandler,} from '@authrim/server/adapters/fastify';
const server = createAuthrimServer({ issuer: 'https://auth.example.com', audience: 'https://api.example.com',});
const app = Fastify();
// Required auth on a single routeapp.get('/api/profile', { preHandler: authrimPreHandler(server), handler: (request, reply) => { reply.send({ sub: request.auth.sub, tokenType: request.authTokenType, }); },});
// Optional auth on a single routeapp.get('/api/posts', { preHandler: authrimOptionalPreHandler(server), handler: (request, reply) => { if (request.auth) { reply.send({ posts: getPostsForUser(request.auth.sub) }); } else { reply.send({ posts: getPublicPosts() }); } },});アプリ全体: authrimPlugin / authrimOptionalPlugin
Fastifyプラグインとして認証を登録し、スコープ内のすべてのルートを保護します:
import { authrimPlugin, authrimOptionalPlugin,} from '@authrim/server/adapters/fastify';
const app = Fastify();
// Protect all routes registered after this pluginapp.register(async (instance) => { await instance.register(authrimPlugin(server));
instance.get('/api/profile', (request, reply) => { reply.send({ sub: request.auth.sub }); });
instance.get('/api/settings', (request, reply) => { reply.send({ settings: getUserSettings(request.auth.sub) }); });});
// Routes outside the scope are unaffectedapp.get('/health', (request, reply) => { reply.send({ status: 'ok' });});リクエストプロパティ
| プロパティ | 型 | 説明 |
|---|---|---|
request.auth | ValidatedToken | undefined | 検証済みトークンのクレーム |
request.authTokenType | 'Bearer' | 'DPoP' | undefined | 使用されたトークンタイプ |
Fastify完全サンプル
import Fastify from 'fastify';import { createAuthrimServer } from '@authrim/server';import { authrimPreHandler, authrimOptionalPreHandler, authrimPlugin,} from '@authrim/server/adapters/fastify';
const server = createAuthrimServer({ issuer: 'https://auth.example.com', audience: 'https://api.example.com',});
const app = Fastify({ logger: true });
// Health check — no authapp.get('/health', (request, reply) => { reply.send({ status: 'ok' });});
// Optional auth routeapp.get('/api/feed', { preHandler: authrimOptionalPreHandler(server), handler: (request, reply) => { reply.send({ user: request.auth?.sub ?? 'anonymous', feed: request.auth ? getPersonalizedFeed(request.auth.sub) : getPublicFeed(), }); },});
// Protected scope using pluginapp.register(async (instance) => { await instance.register(authrimPlugin(server, { realm: 'my-api', onError: (err) => app.log.warn({ code: err.code }, 'Auth failed'), }));
instance.get('/api/profile', (request, reply) => { reply.send({ sub: request.auth.sub, email: request.auth.email }); });
instance.delete('/api/account', (request, reply) => { deleteAccount(request.auth.sub); reply.code(204).send(); });});
app.listen({ port: 3000 });TypeScriptモジュール拡張
import type { ValidatedToken } from '@authrim/server';
declare module 'fastify' { interface FastifyRequest { auth: ValidatedToken; authTokenType: 'Bearer' | 'DPoP'; }}比較: Express vs Fastify
| 項目 | Express | Fastify |
|---|---|---|
| 必須認証(ルートごと) | authrimMiddleware(server) | authrimPreHandler(server) |
| オプション認証(ルートごと) | authrimOptionalMiddleware(server) | authrimOptionalPreHandler(server) |
| アプリ全体の認証 | app.use(authrimMiddleware(server)) | authrimPlugin(server) / authrimOptionalPlugin(server) |
| 認証情報の場所 | req.auth | request.auth |
| トークンタイプの場所 | req.authTokenType | request.authTokenType |
| 型拡張 | declare global { namespace Express } | declare module 'fastify' |
| エラーレスポンス | 401 + WWW-Authenticate | 401 + WWW-Authenticate |
次のステップ
- Hono、Koa & NestJSアダプター — その他のフレームワーク統合
- エラーハンドリング — エラーコードとレスポンスユーティリティ
- 設定リファレンス — サーバー設定オプション