express-jwt vs express-jwt-authz vs jsonwebtoken vs jwt-simple vs koa-jwt vs passport-jwt
Node.js における JWT 認証ミドルウェアとライブラリの選定ガイド
express-jwtexpress-jwt-authzjsonwebtokenjwt-simplekoa-jwtpassport-jwt類似パッケージ:

Node.js における JWT 認証ミドルウェアとライブラリの選定ガイド

jsonwebtoken は JWT の生成・検証を行う低レベルなコアライブラリであり、express-jwtkoa-jwt はこれを Express や Koa フレームワーク用にラップしたミドルウェアです。passport-jwt は Passport.js エコシステム内で戦略として動作し、express-jwt-authz は認可(スコープチェック)に特化しています。一方、jwt-simple は軽量ですが現在では非推奨とされており、セキュリティ上の理由から新規プロジェクトでの使用は避けるべきです。これらを適切に使い分けることで、安全かつ保守性の高い認証システムを構築できます。

npmのダウンロードトレンド

3 年

GitHub Starsランキング

統計詳細

パッケージ
ダウンロード数
Stars
サイズ
Issues
公開日時
ライセンス
express-jwt04,51428.5 kB642年前MIT
express-jwt-authz0987.75 kB10-MIT
jsonwebtoken018,19043.4 kB2089ヶ月前MIT
jwt-simple01,357-347年前MIT
koa-jwt01,35043.2 kB7-MIT
passport-jwt01,97852 kB42-MIT

Node.js における JWT 認証:ライブラリとミドルウェアの完全比較

Node.js で JWT(JSON Web Token)を使った認証システムを構築する際、多くの開発者は「どのパッケージを使うべきか」で迷います。jsonwebtokenexpress-jwtpassport-jwt など、似たような名前や機能を持つパッケージが乱立しているためです。

本記事では、これら主要な 6 つのパッケージを技術的な観点から深く比較します。単なる機能リストではなく、実際のコード例を通じて「いつ、どれを選ぶべきか」を明確にします。

🏗️ 基本構造:コアライブラリ vs フレームワーク特化ミドルウェア

まず大前提として、これらのパッケージは 2 つのカテゴリに分類できます。

  1. コアライブラリ: JWT そのものの処理(署名、検証)を行うもの。
  2. ミドルウェア: 特定の Web フレームワーク(Express, Koa)に組み込み、リクエスト処理を自動化するもの。

jsonwebtoken: すべての中核となるコアライブラリ

jsonwebtoken は、Auth0 によってメンテナンスされている事実上の標準ライブラリです。フレームワークに依存せず、JWT の発行(sign)と検証(verify)のみを行います。

const jwt = require('jsonwebtoken');
const secret = 'my-secret-key';

// トークンの発行
const token = jwt.sign({ userId: 123, role: 'admin' }, secret, { expiresIn: '1h' });

// トークンの検証(ミドルウェア内で手動呼び出しが必要)
try {
  const decoded = jwt.verify(token, secret);
  console.log(decoded); // { userId: 123, role: 'admin', iat: ... }
} catch (err) {
  console.error('Invalid token');
}

選定理由: Express や Koa に限定されない汎用性が必要な場合、あるいはミドルウェアの挙動を完全にカスタマイズしたい場合に必須です。他の多くのパッケージも内部でこれを利用しています。

jwt-simple: ⚠️ 非推奨のパッケージ

jwt-simple は、昔から存在する軽量な JWT ライブラリですが、現在は機能が限定的で、セキュリティアップデートも頻繁ではありません。npm のページやリポジトリでも、より堅牢な jsonwebtoken への移行が事実上推奨されています。

// jwt-simple の例(非推奨)
const jwt = require('jwt-simple');
const token = jwt.encode({ userId: 123 }, 'secret');
const decoded = jwt.decode(token, 'secret');

選定理由: 新規プロジェクトでは使用しないでください。サポートが不十分で、アルゴリズムの柔軟性も低いため、セキュリティリスクになります。必ず jsonwebtoken を選んでください。

🚀 Express アプリケーションでの実装比較

Express を使っている場合、jsonwebtoken を毎回手動で呼び出すのは面倒です。そこでミドルウェアの出番です。

express-jwt: 標準的な自動検証ミドルウェア

express-jwt は、リクエストヘッダーからトークンを抽出し、検証して req.user に結果を格納するまでの処理を自動化します。

const express = require('express');
const { expressjwt: jwt } = require('express-jwt');
const app = express();

// ミドルウェアとして適用
app.use(
  jwt({
    secret: 'my-secret-key',
    algorithms: ['HS256'],
    getToken: (req) => {
      if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
        return req.headers.authorization.split(' ')[1];
      }
      return null;
    }
  })
);

// 保護されたルート
app.get('/profile', (req, res) => {
  // req.user には検証済みのデコードデータが入っている
  res.json({ user: req.user });
});

選定理由: Express で標準的な API 認証を実装する場合、最もシンプルで確実な選択です。ボイラープレートコードを削減できます。

passport-jwt: 複雑な認証戦略の一部として

passport-jwt は、Passport.js エコシステムの一部です。単独で使うというより、ローカル認証(ID/Pass)や Google 認証などと併用し、戦略を切り替えたい場合に威力を発揮します。

const passport = require('passport');
const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt');

const options = {
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  secretOrKey: 'my-secret-key'
};

// 戦略の定義
passport.use(new JwtStrategy(options, (jwt_payload, done) => {
  // ここで DB 検索など追加ロジックを挟める
  User.findById(jwt_payload.userId, (err, user) => {
    if (err) return done(err, false);
    return done(null, user || false);
  });
}));

// ルートでの使用
app.get('/profile', passport.authenticate('jwt', { session: false }), (req, res) => {
  res.json({ user: req.user });
});

選定理由: 単なるトークン検証だけでなく、「トークン内の ID で DB からユーザー情報を再取得する」といった処理や、複数の認証方法を組み合わせたい場合に適しています。設定は express-jwt よりも重くなります。

express-jwt-authz: 認可(Authorization)に特化

express-jwt-authz は、認証(Authentication)ではなく認可(Authorization)を担当します。express-jwt で検証が終わった後、「このユーザーは admin スコープを持っているか?」をチェックするために使います。

const { authz } = require('express-jwt-authz');

// express-jwt の後に適用
app.get('/admin-dashboard', 
  authz(['read:users', 'write:users']), // これらのスコープが必要
  (req, res) => {
    res.json({ message: 'Welcome Admin' });
  }
);

選定理由: スコープベースのアクセス制御が必要な場合、express-jwt とセットで使用します。単体では動作しません。

🌿 Koa フレームワークでの実装

Koa ユーザーが Express 用ミドルウェアを使うと、非同期処理の扱い(Promise vs Callback)で問題が起きます。Koa 専用のパッケージを使いましょう。

koa-jwt: Koa 向けの最適化ミドルウェア

koa-jwt は、express-jwt と同じ機能を提供しますが、Koa の ctx オブジェクトや async/await 構文に最適化されています。

const Koa = require('koa');
const koaJwt = require('koa-jwt');
const app = new Koa();

// Koa ミドルウェアとして適用
app.use(
  koaJwt({
    secret: 'my-secret-key',
    algorithms: ['HS256']
  }).unless({ path: ['/public', '/login'] })
);

app.use(async (ctx) => {
  // ctx.state.user にデコードデータが入る
  ctx.body = { user: ctx.state.user };
});

選定理由: Koa を使っている場合、これ一択です。Express 用パッケージをアダプター経由で使うより、パフォーマンスと保守性の面で優れています。

📊 技術比較サマリー

パッケージ主な役割対応フレームワーク自動ユーザー注入認可機能推奨度
jsonwebtokenコア処理 (Sign/Verify)汎用 (None)❌ (手動)⭐⭐⭐⭐⭐ (必須)
express-jwt認証ミドルウェアExpress✅ (req.user)⭐⭐⭐⭐⭐
koa-jwt認証ミドルウェアKoa✅ (ctx.state.user)⭐⭐⭐⭐⭐ (Koa 限定)
passport-jwt認証戦略Express (Passport)✅ (req.user)⭐⭐⭐⭐ (複雑な場合)
express-jwt-authz認可ミドルウェアExpress❌ (前提必要)⭐⭐⭐⭐ (併用時)
jwt-simpleコア処理 (旧)汎用❌ (非推奨)

💡 実世界でのアーキテクチャ選択パターン

パターン A: シンプルな REST API (Express)

最も一般的な構成です。余計なものは入れず、express-jwt で守ります。

  • 構成: express + express-jwt
  • 理由: 設定が最小限で、パフォーマンスも十分。DB lookup が必要ないステートレス認証に最適。

パターン B: 複雑な権限管理が必要なシステム

ユーザーの役割(Role)やスコープを細かく制御する場合です。

  • 構成: express + express-jwt + express-jwt-authz
  • 理由: 認証と認可を分離することで、コードが整理されます。ミドルウェアチェーンで明確に制御可能です。

パターン C: 複数認証手段を持つプラットフォーム

ログイン(ID/Pass)、ソーシャルログイン、API トークン認証を全て支える場合です。

  • 構成: express + passport + passport-jwt (+ passport-local etc)
  • 理由: 統一されたインターフェースで複数の戦略を扱えるため、拡張性が極めて高いです。

パターン D: Koa ベースのモダンアプリ

  • 構成: koa + koa-jwt
  • 理由: フレームワークの特性を活かした、自然な非同期フローを実現します。

🔒 セキュリティに関する重要な注意点

どのパッケージを選ぶにしても、以下の点は共通して重要です。

  1. アルゴリズムの明示: jsonwebtokenexpress-jwt では、algorithms: ['HS256'] のように使用するアルゴリズムを明示的に指定してください。指定しないと、アルゴリズム_none_ 攻撃などのリスクが高まります。
  2. 秘密鍵の管理: 秘密鍵(secret)やプライベート鍵は、環境変数で管理し、コードにハードコーディングしないでください。
  3. 有効期限の設定: expiresIn を設定し、トークンの寿命を短くすることで、漏洩時のリスクを軽減します。

🏁 結論

  • Express で普通に作るなら: express-jwt がベストプラクティスです。
  • Koa なら: koa-jwt 一択です。
  • もっと柔軟に制御したい、または Passport を使っているなら: jsonwebtoken を直接使うか、passport-jwt を検討します。
  • 絶対に避けるべき: jwt-simple はレガシーなプロジェクトの維持以外は選択肢から外してください。

これらのツールは「車輪の再発明」を防ぎ、セキュリティの落とし穴を埋めるために存在します。プロジェクトの規模と複雑度に合わせて、適切なツールを選ぶことが、堅牢なシステム構築への第一歩です。

選び方: express-jwt vs express-jwt-authz vs jsonwebtoken vs jwt-simple vs koa-jwt vs passport-jwt

  • express-jwt:

    Express アプリケーションで、リクエストごとに JWT を自動検証し、ユーザー情報を req.user に注入したい場合に選択します。設定がシンプルで、標準的な API サーバーの認証ゲートウェイとして最適です。ただし、複雑な認証フローや複数戦略の併用には向きません。

  • express-jwt-authz:

    JWT の検証自体ではなく、検証済みのトークンに含まれる権限(スコープやパーミッション)に基づいてアクセス制御を行いたい場合に使用します。通常は express-jwt と組み合わせて、認証の次のステップとして導入します。

  • jsonwebtoken:

    フレームワークに依存せず、JWT の署名、検証、デコードを自前で制御したい場合に選択します。ミドルウェアとしての自動処理は行わないため、Koa や Fastify など Express 以外の環境や、カスタムな認証ロジックが必要な場合に適しています。

  • jwt-simple:

    非常に軽量な実装ですが、機能制限があり、現在はメンテナンスが停滞しているため新規プロジェクトでの使用は推奨されません。セキュリティリスクを避けるため、代わりに jsonwebtoken の使用を検討すべきです。

  • koa-jwt:

    Koa フレームワークを使用している場合に選択する、express-jwt と同等のミドルウェアです。Koa のミドルウェア構成(async/await ベース)に最適化されており、Express 用パッケージを Koa で無理に使うべきではありません。

  • passport-jwt:

    Passport.js を既に採用しており、ローカル認証、OAuth、JWT など複数の認証戦略を切り替え可能にしたい場合に選択します。単なる JWT 検証だけでなく、セッション管理や複雑な認証フローを統合したいプロジェクトに適しています。

express-jwt のREADME

express-jwt

This module provides Express middleware for validating JWTs (JSON Web Tokens) through the jsonwebtoken module. The decoded JWT payload is available on the request object.

Install

$ npm install express-jwt

API

expressjwt(options)

Options has the following parameters:

  • secret: jwt.Secret | GetVerificationKey (required): The secret as a string or a function to retrieve the secret.
  • getToken?: TokenGetter (optional): A function that receives the express Request and returns the token, by default it looks in the Authorization header.
  • isRevoked?: IsRevoked (optional): A function to verify if a token is revoked.
  • onExpired?: ExpirationHandler (optional): A function to handle expired tokens.
  • credentialsRequired?: boolean (optional): If its false, continue to the next middleware if the request does not contain a token instead of failing, defaults to true.
  • requestProperty?: string (optional): Name of the property in the request object where the payload is set. Default to req.auth.
  • Plus... all the options available in the jsonwebtoken verify function.

The available functions have the following interface:

  • GetVerificationKey = (req: express.Request, token: jwt.Jwt | undefined) => Promise<jwt.Secret>;
  • IsRevoked = (req: express.Request, token: jwt.Jwt | undefined) => Promise<boolean>;
  • TokenGetter = (req: express.Request) => string | Promise<string> | undefined;

Usage

Basic usage using an HS256 secret:

var { expressjwt: jwt } = require("express-jwt");
// or ES6
// import { expressjwt, ExpressJwtRequest } from "express-jwt";

app.get(
  "/protected",
  jwt({ secret: "shhhhhhared-secret", algorithms: ["HS256"] }),
  function (req, res) {
    if (!req.auth.admin) return res.sendStatus(401);
    res.sendStatus(200);
  }
);

The decoded JWT payload is available on the request via the auth property.

The default behavior of the module is to extract the JWT from the Authorization header as an OAuth2 Bearer token.

Required Parameters

The algorithms parameter is required to prevent potential downgrade attacks when providing third party libraries as secrets.

:warning: Do not mix symmetric and asymmetric (ie HS256/RS256) algorithms: Mixing algorithms without further validation can potentially result in downgrade vulnerabilities.

jwt({
  secret: "shhhhhhared-secret",
  algorithms: ["HS256"],
  //algorithms: ['RS256']
});

Additional Options

You can specify audience and/or issuer as well, which is highly recommended for security purposes:

jwt({
  secret: "shhhhhhared-secret",
  audience: "http://myapi/protected",
  issuer: "http://issuer",
  algorithms: ["HS256"],
});

If the JWT has an expiration (exp), it will be checked.

If you are using a base64 URL-encoded secret, pass a Buffer with base64 encoding as the secret instead of a string:

jwt({
  secret: Buffer.from("shhhhhhared-secret", "base64"),
  algorithms: ["RS256"],
});

To only protect specific paths (e.g. beginning with /api), use express router call use, like so:

app.use("/api", jwt({ secret: "shhhhhhared-secret", algorithms: ["HS256"] }));

Or, the other way around, if you want to make some paths unprotected, call unless like so.

app.use(
  jwt({
    secret: "shhhhhhared-secret",
    algorithms: ["HS256"],
  }).unless({ path: ["/token"] })
);

This is especially useful when applying to multiple routes. In the example above, path can be a string, a regexp, or an array of any of those.

For more details on the .unless syntax including additional options, please see express-unless.

This module also support tokens signed with public/private key pairs. Instead of a secret, you can specify a Buffer with the public key

var publicKey = fs.readFileSync("/path/to/public.pub");
jwt({ secret: publicKey, algorithms: ["RS256"] });

Customizing Token Location

A custom function for extracting the token from a request can be specified with the getToken option. This is useful if you need to pass the token through a query parameter or a cookie. You can throw an error in this function and it will be handled by express-jwt.

app.use(
  jwt({
    secret: "hello world !",
    algorithms: ["HS256"],
    credentialsRequired: false,
    getToken: function fromHeaderOrQuerystring(req) {
      if (
        req.headers.authorization &&
        req.headers.authorization.split(" ")[0] === "Bearer"
      ) {
        return req.headers.authorization.split(" ")[1];
      } else if (req.query && req.query.token) {
        return req.query.token;
      }
      return null;
    },
  })
);

Retrieve key dynamically

If you need to obtain the key dynamically from other sources, you can pass a function in the secret parameter with the following parameters:

  • req (Object) - The express request object.
  • token (Object) - An object with the JWT payload and headers.

For example, if the secret varies based on the issuer:

var jwt = require("express-jwt");
var data = require("./data");
var utilities = require("./utilities");

var getSecret = async function (req, token) {
  const issuer = token.payload.iss;
  const tenant = await data.getTenantByIdentifier(issuer);
  if (!tenant) {
    throw new Error("missing_secret");
  }
  return utilities.decrypt(tenant.secret);
};

app.get(
  "/protected",
  jwt({ secret: getSecret, algorithms: ["HS256"] }),
  function (req, res) {
    if (!req.auth.admin) return res.sendStatus(401);
    res.sendStatus(200);
  }
);

Secret rotation

The getSecret callback could also be used in cases where the same issuer might issue tokens with different keys at certain point:

var getSecret = async function (req, token) {
  const { iss } = token.payload;
  const { kid } = token.header;
  // get the verification key by a given key-id and issuer.
  return verificationKey;
};

Revoked tokens

It is possible that some tokens will need to be revoked so they cannot be used any longer. You can provide a function as the isRevoked option. The signature of the function is function(req, payload, done):

  • req (Object) - The express request object.
  • token (Object) - An object with the JWT payload and headers.

For example, if the (iss, jti) claim pair is used to identify a JWT:

const jwt = require("express-jwt");
const data = require("./data");

const isRevokedCallback = async (req, token) => {
  const issuer = token.payload.iss;
  const tokenId = token.payload.jti;
  const token = await data.getRevokedToken(issuer, tokenId);
  return token !== "undefined";
};

app.get(
  "/protected",
  jwt({
    secret: "shhhhhhared-secret",
    algorithms: ["HS256"],
    isRevoked: isRevokedCallback,
  }),
  function (req, res) {
    if (!req.auth.admin) return res.sendStatus(401);
    res.sendStatus(200);
  }
);

Handling expired tokens

You can handle expired tokens as follows:

  jwt({
    secret: "shhhhhhared-secret",
    algorithms: ["HS256"],
    onExpired: async (req, err) => {
      if (new Date() - err.inner.expiredAt < 5000) { return;}
      throw err;
    },,
  })

Error handling

The default behavior is to throw an error when the token is invalid, so you can add your custom logic to manage unauthorized access as follows:

app.use(function (err, req, res, next) {
  if (err.name === "UnauthorizedError") {
    res.status(401).send("invalid token...");
  } else {
    next(err);
  }
});

You might want to use this module to identify registered users while still providing access to unregistered users. You can do this by using the option credentialsRequired:

app.use(
  jwt({
    secret: "hello world !",
    algorithms: ["HS256"],
    credentialsRequired: false,
  })
);

Typescript

A Request type is provided from express-jwt, which extends express.Request with the auth property. It could be aliased, like how JWTRequest is below.

import { expressjwt, Request as JWTRequest } from "express-jwt";

app.get(
  "/protected",
  expressjwt({ secret: "shhhhhhared-secret", algorithms: ["HS256"] }),
  function (req: JWTRequest, res: express.Response) {
    if (!req.auth?.admin) return res.sendStatus(401);
    res.sendStatus(200);
  }
);

Migration from v6

  1. The middleware function is now available as a named import rather than a default one: import { expressjwt } from 'express-jwt'
  2. The decoded JWT payload is now available as req.auth rather than req.user
  3. The secret function had (req, header, payload, cb), now it can return a promise and receives (req, token). token has header and payload.
  4. The isRevoked function had (req, payload, cb), now it can return a promise and receives (req, token). token has header and payload.

Related Modules

Tests

$ npm install
$ npm test

Contributors

Check them out here

Issue Reporting

If you have found a bug or if you have a feature request, please report them at this repository issues section. Please do not report security vulnerabilities on the public GitHub issue tracker. The Responsible Disclosure Program details the procedure for disclosing security issues.

Author

Auth0

License

This project is licensed under the MIT license. See the LICENSE file for more info.