はじめに
認証ミドルウェアは受信リクエストとルートハンドラーの間に位置し、保護されたリソースへのアクセスを許可する前に、ユーザーが主張通りの本人であることを確認します。このレッスンでは、JWTベースの認証ミドルウェアを構築し、特定のルートに適用することで、オープンなAPIをセキュアで本番レディのシステムへと変換します。
ミドルウェア関数は、リクエストオブジェクト、レスポンスオブジェクト、およびスタック内の次のミドルウェア関数にアクセスできます。認証ミドルウェアはAuthorizationヘッダーを検査し、トークンを検証し、ユーザー情報をリクエストに付加し、次のハンドラーに制御を渡すか、リクエストを拒否します。
認証ミドルウェアの構築
JWTトークンを抽出および検証するミドルウェア関数を作成します。ミドルウェアはAuthorizationヘッダー内のBearerトークンをチェックし、秘密鍵を使用して検証し、デコードされたペイロードをリクエストオブジェクトに付加して、 downstreamハンドラーが使用できるようにします。
javascript
const jwt = require('jsonwebtoken');
const authenticate = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.substring(7);
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid or expired token' });
}
};
module.exports = authenticate;
ミドルウェアはBearerプレフィックスを取り除くことでトークンを抽出し、秘密鍵に対して検証し、デコードされたユーザーデータを`req.user`に格納します。検証が失敗した場合、`next`を呼び出さずに401ステータスを返し、リクエストパイプラインを停止します。

ミドルウェアによるルートの保護
ユーザーIDが必要なルートに認証ミドルウェアを適用します。ミドルウェアをハンドラー関数の前に挿入することで、個別のルート、ルートのグループ、またはルーター全体を保護できます。
javascript
const express = require('express');
const authenticate = require('./middleware/authenticate');
const router = express.Router();
router.get('/profile', authenticate, (req, res) => {
res.json({
userId: req.user.id,
email: req.user.email,
message: 'This is your protected profile'
});
});
router.post('/posts', authenticate, async (req, res) => {
const post = await createPost({
...req.body,
authorId: req.user.id
});
res.status(201).json(post);
});
router.get('/public', (req, res) => {
res.json({ message: 'This endpoint is public' });
});
`authenticate`ミドルウェアはルートハンドラーの前に実行されます。ミドルウェアのないルートは公開アクセスのままです。ミドルウェアによって設定される`req.user`オブジェクトにより、ハンドラーは認証済みユーザーのIDにアクセスできます。
ロールベースのアクセス制御
ユーザー権限やパーミッションを確認して認可を処理するようにミドルウェアを拡張します。必要なロールを受け取り、それらの要件を強制するミドルウェアを返すラッパー関数を作成します。
javascript
const authorize = (...allowedRoles) => {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
}
if (!allowedRoles.includes(req.user.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
};
router.delete('/users/:id', authenticate, authorize('admin'), async (req, res) => {
await deleteUser(req.params.id);
res.status(204).send();
});
router.put('/posts/:id', authenticate, authorize('admin', 'editor'), async (req, res) => {
const post = await updatePost(req.params.id, req.body);
res.json(post);
});
`authorize`ミドルウェアは、`authenticate`がすでに実行されて`req.user`が付加されていることを前提としています。ユーザーのロールを許可された値と照合し、ロールが一致しない場合は403 Forbiddenを返します。複数のミドルウェア関数をチェーン化して、認証と認可の両方を強制します。
エラー処理とトークンのリフレッシュ
一般的な認証シナリオを適切に処理します。トークンの欠如、無効なトークン、有効期限切れのトークンを区別し、明確なフィードバックを提供します。長期間有効なセッションのためにリフレッシュトークンの実装を検討してください。
javascript
const authenticate = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({
error: 'No token provided',
code: 'TOKEN_MISSING'
});
}
const token = authHeader.substring(7);
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
if (error.name === 'TokenExpiredError') {
return res.status(401).json({
error: 'Token expired',
code: 'TOKEN_EXPIRED'
});
}
return res.status(401).json({
error: 'Invalid token',
code: 'TOKEN_INVALID'
});
}
};
エラーコードを含む構造化されたエラーレスポンスは、クライアントがさまざまな失敗モードを適切に処理するのに役立ちます。有効期限切れのトークンはトークンリフレッシュフローを引き起こす可能性があり、無効なトークンは再認証が必要です。
まとめ
認証ミドルウェアはセキュリティロジックを一元化し、API全体で一貫して適用します。JWTトークンの抽出と検証、ユーザー情報のリクエストへの付加、ロールベースのアクセス制御の強制により、オープンなエンドポイントをセキュアなシステムに変換します。認可の前に認証を必ず実行し、明確なエラーレスポンスを返し、本番環境ではトークンの有効期限とリフレッシュ戦略を検討してください。