#Web制作 約6分で読めます
Astro Middleware で認証・認可を実装する方法|JWT・セッション・OAuth完全ガイド【2026年4月】
Astro MiddlewareでJWT・セッション・OAuth2.0を使った認証を実装する手順を解説。Astro 5.2のMiddleware型安全性強化とcontext.locals改善を活用した実装例を紹介
Astroで認証機能を実装する際、Middlewareを使った実装が主流になっています。本記事では、Astro MiddlewareでJWT認証・セッションベース認証・OAuth2.0を実装する具体的な手順と、最新のベストプラクティスを解説します。
Astro Middleware とは|認証実装に最適な理由
Astro Middlewareは、リクエストとレスポンスの間で実行される関数で、認証チェック・ログ記録・ヘッダー追加などの共通処理を一元管理できます。
Middlewareの基本構造
// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
export const onRequest = defineMiddleware(async (context, next) => {
// リクエスト前処理
console.log(`Request: ${context.url.pathname}`);
// 次のハンドラーへ
const response = await next();
// レスポンス後処理
return response;
});
認証実装に最適な3つの理由
- 全ルートで一元管理:
/api/*や/dashboard/*など特定パスに対して認証チェックを適用 - context.locals で状態共有: 認証情報をページコンポーネントやAPIエンドポイントに渡せる
- SSR・SSG両対応: ハイブリッドレンダリングでも動作
flowchart LR
A[クライアント] --> B[Middleware]
B --> C{認証チェック}
C -->|成功| D[ページレンダリング]
C -->|失敗| E[401/リダイレクト]
D --> F[レスポンス]
E --> F
Astro Middlewareの実行フロー: リクエストごとに認証チェックを挟み、context.localsで認証情報を共有
JWT認証の実装|トークンベース認証の基本
JWTはステートレスな認証方式で、APIサーバーとの連携に適しています。
JWT検証Middlewareの実装
// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
import { jwtVerify } from 'jose';
const JWT_SECRET = new TextEncoder().encode(
import.meta.env.JWT_SECRET
);
export const onRequest = defineMiddleware(async (context, next) => {
// 公開ページは認証不要
if (context.url.pathname.startsWith('/login')) {
return next();
}
// Cookieからトークン取得
const token = context.cookies.get('auth_token')?.value;
if (!token) {
// 未認証時はログインページへリダイレクト
return context.redirect('/login');
}
try {
// JWT検証
const { payload } = await jwtVerify(token, JWT_SECRET);
// context.localsにユーザー情報を保存
context.locals.user = {
id: payload.sub as string,
email: payload.email as string,
role: payload.role as string,
};
return next();
} catch (error) {
// トークン無効時は認証エラー
console.error('JWT verification failed:', error);
return new Response('Unauthorized', { status: 401 });
}
});
ログインAPIエンドポイントの実装
// src/pages/api/login.ts
import type { APIRoute } from 'astro';
import { SignJWT } from 'jose';
const JWT_SECRET = new TextEncoder().encode(
import.meta.env.JWT_SECRET
);
export const POST: APIRoute = async ({ request, cookies }) => {
const { email, password } = await request.json();
// パスワード検証(実際はハッシュ化して比較)
const user = await verifyCredentials(email, password);
if (!user) {
return new Response('Invalid credentials', { status: 401 });
}
// JWT生成
const token = await new SignJWT({
sub: user.id,
email: user.email,
role: user.role,
})
.setProtectedHeader({ alg: 'HS256' })
.setExpirationTime('7d')
.sign(JWT_SECRET);
// HttpOnly Cookieにセット
cookies.set('auth_token', token, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 60 * 60 * 24 * 7, // 7日間
path: '/',
});
return new Response(JSON.stringify({ success: true }), {
status: 200,
});
};
ページでユーザー情報を取得
---
// src/pages/dashboard.astro
const user = Astro.locals.user;
if (!user) {
return Astro.redirect('/login');
}
---
<html>
<body>
<h1>Welcome, {user.email}</h1>
<p>Your role: {user.role}</p>
</body>
</html>
セッションベース認証の実装|Redis・KVストアとの連携
セッションベース認証は、サーバー側でセッション情報を管理する従来型の方式です。
Redis セッションストアの実装
// src/lib/session.ts
import { createClient } from 'redis';
const redis = createClient({
url: import.meta.env.REDIS_URL,
});
await redis.connect();
export async function createSession(userId: string): Promise<string> {
const sessionId = crypto.randomUUID();
// セッションデータをRedisに保存(有効期限30分)
await redis.set(
`session:${sessionId}`,
JSON.stringify({ userId, createdAt: Date.now() }),
{ EX: 1800 }
);
return sessionId;
}
export async function getSession(sessionId: string) {
const data = await redis.get(`session:${sessionId}`);
return data ? JSON.parse(data) : null;
}
export async function deleteSession(sessionId: string) {
await redis.del(`session:${sessionId}`);
}
セッション検証Middleware
// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
import { getSession } from './lib/session';
export const onRequest = defineMiddleware(async (context, next) => {
const sessionId = context.cookies.get('session_id')?.value;
if (!sessionId) {
return context.redirect('/login');
}
const session = await getSession(sessionId);
if (!session) {
// セッション期限切れ
return context.redirect('/login');
}
// ユーザー情報をDBから取得
const user = await getUserById(session.userId);
context.locals.user = user;
return next();
});
セッションベース vs JWT|選択基準
| 項目 | セッションベース | JWT |
|---|---|---|
| サーバー状態 | ステートフル(Redis等が必要) | ステートレス |
| スケーラビリティ | Redisクラスタが必要 | 高い(サーバー側で状態不要) |
| セキュリティ | サーバー側で即座に無効化可能 | トークン期限まで無効化不可 |
| 適用場面 | 管理画面・高セキュリティ要件 | API認証・マイクロサービス |
OAuth2.0 認証の実装|Google・GitHub ログイン
OAuth2.0は外部プロバイダー(Google、GitHub等)を使った認証方式です。
OAuth フロー図
sequenceDiagram
participant U as ユーザー
participant A as Astroアプリ
participant G as Google OAuth
participant DB as データベース
U->>A: ログインボタンクリック
A->>G: 認証リクエスト(client_id + redirect_uri)
G->>U: Googleログイン画面
U->>G: 認証・権限承認
G->>A: 認可コード返却(callback)
A->>G: アクセストークン要求(code + client_secret)
G->>A: アクセストークン発行
A->>G: ユーザー情報取得
G->>A: プロフィール情報
A->>DB: ユーザー登録/更新
A->>U: セッション発行・リダイレクト
OAuth2.0の認証フロー: 認可コードを使った安全なトークン取得プロセス
Google OAuth 実装例
// src/pages/api/auth/google.ts
import type { APIRoute } from 'astro';
const GOOGLE_CLIENT_ID = import.meta.env.GOOGLE_CLIENT_ID;
const GOOGLE_CLIENT_SECRET = import.meta.env.GOOGLE_CLIENT_SECRET;
const REDIRECT_URI = 'https://example.com/api/auth/google/callback';
export const GET: APIRoute = async () => {
const authUrl = new URL('https://accounts.google.com/o/oauth2/v2/auth');
authUrl.searchParams.set('client_id', GOOGLE_CLIENT_ID);
authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('scope', 'openid email profile');
return Response.redirect(authUrl.toString());
};
コールバック処理とユーザー情報取得
// src/pages/api/auth/google/callback.ts
import type { APIRoute } from 'astro';
export const GET: APIRoute = async ({ url, cookies }) => {
const code = url.searchParams.get('code');
if (!code) {
return new Response('Authorization code missing', { status: 400 });
}
// アクセストークン取得
const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
code,
client_id: import.meta.env.GOOGLE_CLIENT_ID,
client_secret: import.meta.env.GOOGLE_CLIENT_SECRET,
redirect_uri: 'https://example.com/api/auth/google/callback',
grant_type: 'authorization_code',
}),
});
const { access_token } = await tokenResponse.json();
// ユーザー情報取得
const userResponse = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
headers: { Authorization: `Bearer ${access_token}` },
});
const userData = await userResponse.json();
// DBにユーザー登録/更新
const user = await upsertUser({
email: userData.email,
name: userData.name,
picture: userData.picture,
});
// セッション発行
const sessionId = await createSession(user.id);
cookies.set('session_id', sessionId, {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 7,
path: '/',
});
return Response.redirect('/dashboard');
};
ロールベースアクセス制御(RBAC)の実装
認証後、ユーザーの権限に応じたアクセス制御を実装します。
RBAC Middleware
// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
import { sequence } from 'astro:middleware';
const authMiddleware = defineMiddleware(async (context, next) => {
// JWT検証(前述の実装)
// context.locals.user にユーザー情報を保存
return next();
});
const rbacMiddleware = defineMiddleware(async (context, next) => {
const user = context.locals.user;
// 管理者専用ページのアクセス制御
if (context.url.pathname.startsWith('/admin')) {
if (user?.role !== 'admin') {
return new Response('Forbidden', { status: 403 });
}
}
return next();
});
// Middlewareをチェーン
export const onRequest = sequence(authMiddleware, rbacMiddleware);
パーミッションチェック関数
// src/lib/permissions.ts
type Permission = 'read' | 'write' | 'delete' | 'admin';
const rolePermissions: Record<string, Permission[]> = {
admin: ['read', 'write', 'delete', 'admin'],
editor: ['read', 'write'],
viewer: ['read'],
};
export function hasPermission(role: string, permission: Permission): boolean {
return rolePermissions[role]?.includes(permission) ?? false;
}
ページでの権限チェック
---
// src/pages/admin/users.astro
import { hasPermission } from '@/lib/permissions';
const user = Astro.locals.user;
if (!user || !hasPermission(user.role, 'admin')) {
return Astro.redirect('/unauthorized');
}
---
<html>
<body>
<h1>User Management</h1>
<!-- 管理者専用コンテンツ -->
</body>
</html>
セキュリティベストプラクティス
CSRF対策
// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
export const onRequest = defineMiddleware(async (context, next) => {
if (context.request.method !== 'GET') {
const csrfToken = context.request.headers.get('X-CSRF-Token');
const sessionCsrfToken = context.cookies.get('csrf_token')?.value;
if (!csrfToken || csrfToken !== sessionCsrfToken) {
return new Response('CSRF token mismatch', { status: 403 });
}
}
return next();
});
レート制限の実装
// src/lib/rate-limit.ts
import { createClient } from 'redis';
const redis = createClient({ url: import.meta.env.REDIS_URL });
await redis.connect();
export async function checkRateLimit(
ip: string,
limit: number = 100,
window: number = 3600
): Promise<boolean> {
const key = `rate_limit:${ip}`;
const current = await redis.incr(key);
if (current === 1) {
await redis.expire(key, window);
}
return current <= limit;
}
// src/middleware.ts
import { checkRateLimit } from './lib/rate-limit';
export const onRequest = defineMiddleware(async (context, next) => {
const ip = context.clientAddress;
if (!(await checkRateLimit(ip))) {
return new Response('Too Many Requests', { status: 429 });
}
return next();
});
セキュリティヘッダーの設定
// src/middleware.ts
export const onRequest = defineMiddleware(async (context, next) => {
const response = await next();
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('X-XSS-Protection', '1; mode=block');
response.headers.set(
'Strict-Transport-Security',
'max-age=31536000; includeSubDomains'
);
response.headers.set(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' 'unsafe-inline'"
);
return response;
});
まとめ
Astro Middlewareを使った認証実装の要点:
- JWT認証: ステートレスでスケーラブル、API認証に最適
- セッションベース認証: サーバー側で厳密に管理、即座に無効化可能
- OAuth2.0: 外部プロバイダーを活用、実装コストを削減
- RBAC: ロールベースでアクセス制御を一元管理
- セキュリティ対策: CSRF・レート制限・セキュリティヘッダーを必ず実装
認証方式の選択は、アプリケーションの要件・スケール・セキュリティポリシーに応じて決定してください。