コンテンツにスキップ

Express & Fastifyアダプター

概要

@authrim/serverExpressFastify 向けのファーストクラスアダプターを提供します。両アダプターは受信するアクセストークン(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);
},
};
オプションデフォルト説明
realmstringundefinedWWW-Authenticate レスポンスヘッダーに含まれるRealm値
onError(error: AuthrimServerError) => voidundefined検証失敗時に呼び出されるコールバック(ロギング、メトリクス用)

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 /api
app.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.authundefined になります:

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.authValidatedToken | 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 auth
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
// Optional auth — enhanced for logged-in users
app.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 routes
app.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.authreq.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 route
app.get('/api/profile', {
preHandler: authrimPreHandler(server),
handler: (request, reply) => {
reply.send({
sub: request.auth.sub,
tokenType: request.authTokenType,
});
},
});
// Optional auth on a single route
app.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 plugin
app.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 unaffected
app.get('/health', (request, reply) => {
reply.send({ status: 'ok' });
});

リクエストプロパティ

プロパティ説明
request.authValidatedToken | 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 auth
app.get('/health', (request, reply) => {
reply.send({ status: 'ok' });
});
// Optional auth route
app.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 plugin
app.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

項目ExpressFastify
必須認証(ルートごと)authrimMiddleware(server)authrimPreHandler(server)
オプション認証(ルートごと)authrimOptionalMiddleware(server)authrimOptionalPreHandler(server)
アプリ全体の認証app.use(authrimMiddleware(server))authrimPlugin(server) / authrimOptionalPlugin(server)
認証情報の場所req.authrequest.auth
トークンタイプの場所req.authTokenTyperequest.authTokenType
型拡張declare global { namespace Express }declare module 'fastify'
エラーレスポンス401 + WWW-Authenticate401 + WWW-Authenticate

次のステップ