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

JWT Authentication

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

インストール

pnpm add @zeltjs/auth-jwt

クイックスタート

1. secretを設定する

JWT_SECRET 環境変数を設定します。

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

2. middlewareを登録する

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

3. トークンを発行する

ログイン時、JwtService を使ってトークンに署名します。

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(req = request(LoginSchema)) {
    const body = await req.body();
    const user = await validateCredentials(body.email, body.password);
    if (!user) {
      throw new HTTPException(401, { message: 'Invalid credentials' });
    }
    
    const token = await this.jwtService.sign({
      sub: user.id,
      roles: user.roles,
    });
    
    return { token };
  }
}

4. ルートを保護する

認証を必須にするには @Authorized() を使います。

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

JwtService API

メソッド説明
sign(payload)署名付きJWTトークンを作成する
verify(token)トークンを検証してデコードする(無効な場合は例外を投げる)
decode(token)検証なしでデコードする(エラー時は null を返す)

Sign

カスタムpayloadで署名付きトークンを作成します。

  async createToken(userId: string) {
    return this.jwtService.sign({
      sub: userId,
      roles: ['admin', 'user'],
      customClaim: 'value',
    });
  }
}

Verify

トークンを検証し、payloadを取得します(無効または期限切れの場合は例外を投げます)。

  async validateToken(token: string) {
    try {
      const payload = await this.jwtService.verify(token);
      console.log(payload.sub);
      return payload;
    } catch {
      return null;
    }
  }
}

Decode

検証なしでデコードします(期限切れのトークンを読む際に便利です)。

  readToken(token: string) {
    const payload = this.jwtService.decode(token);
    if (payload) {
      console.log(payload.sub);
    }
    return payload;
  }
}

設定

JwtConfig を継承して動作をカスタマイズします。

@Config
class CustomJwtConfig extends JwtConfig {
  constructor(private userRepo = inject(UserRepository)) {
    super();
  }

  override get secret(): string {
    return this.env.getRequired('JWT_SECRET');
  }

  override get expiresIn(): string {
    return '7d';
  }

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

カスタムconfigを登録します。

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

Configuration Options

オプションデフォルト説明
secretstringenv.getRequired('JWT_SECRET')署名用のsecret key
expiresInstring'1h'トークンの有効期限(例: '15m''7d')
resolveUserfunction{ user: sub, roles: [] } を返すJWTのpayloadからユーザーを解決する

クライアント側の統合

トークンの送信

クライアントは Authorization ヘッダーにトークンを含める必要があります。

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

トークンの保存

クライアント上ではトークンを安全に保存してください。

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

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

長期間のセッションでは、refresh tokenのフローを実装します。

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

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

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

    return { accessToken };
  }
}

エラーレスポンス

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

Edge Runtimeのサポート

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

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