メインコンテンツまでスキップ

JWT認証

@zeltjs/auth-jwtはSPA、モバイルアプリ、API向けのステートレスなJWTベース認証を提供します。

インストール

pnpm add @zeltjs/auth-jwt

クイックスタート

1. シークレットを設定

JWT_SECRET環境変数を設定:

# .env
JWT_SECRET=your-secret-key-at-least-32-characters

2. ミドルウェアを登録

import { createApp } from '@zeltjs/core';
import { JwtMiddleware, JwtConfig } from '@zeltjs/auth-jwt';

const app = createApp({
http: {
controllers: [AuthController, UserController],
middlewares: [JwtMiddleware],
},
configs: [JwtConfig],
});

3. トークンを生成

ログイン時にJwtServiceを使用してトークンに署名:

import { Controller, Post, bodyParam, inject } from '@zeltjs/core';
import { JwtService } from '@zeltjs/auth-jwt';
import * as v from 'valibot';

const LoginSchema = v.object({
email: v.pipe(v.string(), v.email()),
password: v.string(),
});

@Controller('/auth')
class AuthController {
constructor(private jwtService = inject(JwtService)) {}

@Post('/login')
async login(body = bodyParam(LoginSchema)) {
const user = await validateCredentials(body.email, body.password);
if (!user) {
throw new HTTPException(401, { message: '認証情報が無効です' });
}

const token = await this.jwtService.sign({
sub: user.id,
roles: user.roles,
});

return { token };
}
}

4. ルートを保護

@Authorized()を使用して認証を要求:

import { Controller, Get, Authorized, currentUser } from '@zeltjs/core';

@Controller('/users')
class UserController {
@Authorized()
@Get('/me')
me(user = currentUser()) {
return user;
}
}

JwtService API

メソッド説明
sign(payload)署名済みJWTトークンを作成
verify(token)トークンを検証・デコード(無効な場合はスロー)
decode(token)検証なしでデコード(エラー時はnullを返す)

sign

カスタムペイロードで署名済みトークンを作成:

const token = await jwtService.sign({
sub: user.id,
roles: ['admin', 'user'],
customClaim: 'value',
});

verify

トークンを検証してペイロードを取得(無効または期限切れの場合はスロー):

try {
const payload = await jwtService.verify(token);
console.log(payload.sub); // ユーザーID
} catch (error) {
// トークンが無効または期限切れ
}

decode

検証なしでデコード(期限切れトークンの読み取りに便利):

const payload = jwtService.decode(token);
if (payload) {
console.log(payload.sub);
}

設定

JwtConfigを継承して動作をカスタマイズ:

import { JwtConfig, type JwtPayload, type ResolveUserResult } from '@zeltjs/auth-jwt';
import { Config } from '@zeltjs/core';

@Config
class CustomJwtConfig extends JwtConfig {
override get secret(): string {
return process.env.JWT_SECRET!;
}

override get expiresIn(): string {
return '7d'; // トークン有効期限(デフォルト: '1h')
}

override get resolveUser(): (payload: JwtPayload) => Promise<ResolveUserResult> {
return async (payload) => {
const user = await db.users.findById(payload.sub);
return {
user: { id: user.id, name: user.name, email: user.email },
roles: user.roles,
};
};
}
}

カスタム設定を登録:

const app = createApp({
http: {
controllers: [AuthController, UserController],
middlewares: [JwtMiddleware],
},
configs: [CustomJwtConfig], // JwtConfigを置き換え
});

設定オプション

オプションデフォルト説明
secretstringprocess.env.JWT_SECRET署名用シークレットキー
expiresInstring'1h'トークン有効期限(例: '15m', '7d'
resolveUserfunction{ user: sub, roles: [] }を返すJWTペイロードからユーザーを解決

クライアント連携

トークンの送信

クライアントはAuthorizationヘッダーにトークンを含める:

fetch('/api/users/me', {
headers: {
'Authorization': `Bearer ${token}`,
},
});

トークンの保存

クライアントでトークンを安全に保存:

プラットフォーム推奨ストレージ
ブラウザSPAhttpOnly Cookieまたはメモリ(localStorageは避ける)
モバイルアプリセキュアストレージ(Keychain / Keystore)
サーバー間通信環境変数

トークンリフレッシュパターン

長期セッションの場合、リフレッシュトークンフローを実装:

@Controller('/auth')
class AuthController {
constructor(private jwtService = inject(JwtService)) {}

@Post('/refresh')
async refresh(body = bodyParam(RefreshSchema)) {
const payload = await this.jwtService.verify(body.refreshToken);

const user = await db.users.findById(payload.sub);
const accessToken = await this.jwtService.sign({
sub: user.id,
roles: user.roles,
});

return { accessToken };
}
}

エラーレスポンス

ステータスコード条件
401UNAUTHORIZEDトークンなし、無効なトークン、または期限切れトークン
403FORBIDDEN有効なトークンだが必要なロールがない
{
"code": "UNAUTHORIZED",
"message": "Authentication required"
}

エッジランタイムサポート

@zeltjs/auth-jwtはWeb Crypto APIをサポートするjoseライブラリを使用しており、以下と互換性があります:

  • Cloudflare Workers
  • Vercel Edge Functions
  • Deno Deploy
  • Node.js