express-jwt vs jose vs jsonwebtoken vs jwa vs passport-jwt
Node.js における JWT 実装ライブラリの比較
express-jwtjosejsonwebtokenjwapassport-jwt類似パッケージ:

Node.js における JWT 実装ライブラリの比較

express-jwtjosejsonwebtokenjwapassport-jwt はすべて Node.js 環境で JWT(JSON Web Token)を扱うための npm パッケージですが、それぞれ役割と抽象度が大きく異なります。josejsonwebtoken は JWT の署名・検証といったコア機能を提供する汎用ライブラリです。一方、express-jwt は Express 専用のミドルウェア、passport-jwt は Passport 認証戦略として動作し、フレームワークに強く依存します。jwa は低レベルの署名アルゴリズム実装に特化しており、通常はアプリケーションコードで直接使用されません。

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

3 年

GitHub Starsランキング

統計詳細

パッケージ
ダウンロード数
Stars
サイズ
Issues
公開日時
ライセンス
express-jwt04,51228.5 kB642年前MIT
jose07,641258 kB12ヶ月前MIT
jsonwebtoken018,16943.4 kB2016ヶ月前MIT
jwa010214.1 kB171年前MIT
passport-jwt01,97952 kB43-MIT

JWT 実装ライブラリ比較: express-jwt、jose、jsonwebtoken、jwa、passport-jwt

JWT(JSON Web Token)は認証や情報交換に広く使われる仕組みですが、Node.js エコシステムには複数の実装があります。それぞれ目的や抽象度が異なり、選択を誤るとセキュリティリスクやメンテナンス負荷につながります。ここでは、express-jwtjosejsonwebtokenjwapassport-jwt の5つのパッケージを、実際の開発現場の観点から深く比較します。

🔑 コア機能:署名・検証の実装方式

jose

RFC準拠の最新実装で、JWS/JWE/JWK/JWA/JWT など JWT 周辺のすべての標準を網羅しています。Web Crypto API を内部で使い、ESM と CJS の両方をサポート。TypeScript ファーストで設計されており、型安全性が高いです。

// jose: 署名と検証
import { SignJWT, jwtVerify } from 'jose';

const secret = new TextEncoder().encode('your-secret');

// 署名
const jwt = await new SignJWT({ userId: 123 })
  .setProtectedHeader({ alg: 'HS256' })
  .setIssuedAt()
  .setExpirationTime('1h')
  .sign(secret);

// 検証
const { payload } = await jwtVerify(jwt, secret);

jsonwebtoken

最も普及しているシンプルな JWT ライブラリ。署名・検証・デコードの基本機能に特化しており、API は直感的です。ただし、アルゴリズムの選択ミスによる脆弱性(例: none アルゴリズム)に注意が必要です。

// jsonwebtoken: 署名と検証
import jwt from 'jsonwebtoken';

const token = jwt.sign({ userId: 123 }, 'your-secret', { expiresIn: '1h' });
const decoded = jwt.verify(token, 'your-secret');

jwa

低レベルの署名プリミティブを提供するライブラリ。JWT 全体ではなく、JWA(JSON Web Algorithms)仕様に基づく署名/検証ロジックだけを実装しています。通常は直接使うことはなく、他の JWT ライブラリの内部で使われます。

// jwa: 低レベル署名
import jwa from 'jwa';

const signer = jwa('HS256');
const signature = signer.sign('payload', 'secret');
const isValid = signer.verify('payload', signature, 'secret');

express-jwt

Express ミドルウェア専用のライブラリ。jsonwebtoken をラップして、リクエストヘッダーから JWT を取り出し、検証し、req.user に結果をセットします。

// express-jwt: Express ミドルウェア
import expressJwt from 'express-jwt';

app.use(
  expressJwt({ secret: 'your-secret', algorithms: ['HS256'] })
);

app.get('/protected', (req, res) => {
  // req.auth にペイロードが入る
  res.json(req.auth);
});

passport-jwt

Passport 認証戦略として動作します。Passport のエコシステムと統合され、柔軟な認証フローを構築できます。トークンの取得元(ヘッダー、クッキー、ボディなど)をカスタマイズ可能です。

// passport-jwt: Passport 戦略
import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt';

const opts = {
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  secretOrKey: 'your-secret'
};

passport.use(new JwtStrategy(opts, (payload, done) => {
  // payload は検証済みの JWT ペイロード
  return done(null, payload);
}));

app.get('/protected', passport.authenticate('jwt', { session: false }), (req, res) => {
  res.json(req.user);
});

⚙️ アーキテクチャとの整合性

Express 専用 vs 一般用途

  • express-jwtpassport-jwt は、それぞれ Express と Passport という特定のフレームワークに強く依存しています。これらを使う場合、アプリケーションがそのエコシステムに縛られます。
  • josejsonwebtoken はフレームワーク非依存で、どこでも使えます。例えば Next.js API Routes や Cloudflare Workers など、Express 以外の環境でも問題なく動作します。

高レベル vs 低レベル

  • josejsonwebtoken は高レベル API を提供し、JWT の生成・検証を1〜2行で完結させられます。
  • jwa は署名アルゴリズムの実装のみに焦点を当てており、JWT 全体の処理には不向きです。新しいプロジェクトで直接使うべきではありません。

🛡️ セキュリティとベストプラクティス

アルゴリズムの扱い

  • jose は、明示的にアルゴリズムを指定しないと動作しません。また、none アルゴリズムはデフォルトで無効化されています。
  • jsonwebtoken は、verify() 時に algorithms オプションを必ず指定すべきです。これを忘れると、攻撃者が none アルゴリズムを使った偽造トークンを送り込める可能性があります。
// jsonwebtoken: 安全な verify
jwt.verify(token, secret, { algorithms: ['HS256'] });

秘密鍵の管理

  • jose は JWK(JSON Web Key)形式をネイティブサポートしており、公開鍵/秘密鍵ペアや証明書ベースの検証が容易です。
  • jsonwebtoken も PEM 形式や JWK をサポートしていますが、jose の方が型安全かつ RFC 準拠度が高いです。

🧩 統合シナリオ別推奨

シナリオ1: Express + 単純な JWT 認証

  • 推奨: express-jwt
  • 理由: 最小限のコードでミドルウェアを設定でき、req.auth で即座にユーザー情報を参照可能。
app.use(expressJwt({ secret: process.env.JWT_SECRET, algorithms: ['HS256'] }));

シナリオ2: Passport との統合(OAuth など複数戦略)

  • 推奨: passport-jwt
  • 理由: Passport の既存インフラとシームレスに連携でき、複数の認証方法を併用しやすい。

シナリオ3: フレームワーク非依存 or モダン環境(Next.js, Workers)

  • 推奨: jose
  • 理由: ESM 対応、TypeScript ファースト、RFC 準拠で将来性が高い。特に JWK や RS256 などの公開鍵方式を使う場合に最適。

シナリオ4: レガシー保守 or 単純な HS256

  • 推奨: jsonwebtoken
  • 理由: 多くのチュートリアルや既存コードで使われており、学習コストが低い。ただし、algorithms オプションを忘れないように注意。

シナリオ5: 低レベル署名操作

  • 非推奨: jwa
  • 理由: JWT 全体の処理には不向き。新しいプロジェクトでは直接使用すべきではない。

📌 まとめ:選択の指針

パッケージ用途推奨度注意点
joseモダン・RFC準拠・フレームワーク非依存⭐⭐⭐⭐⭐学習曲線やや高め
jsonwebtokenシンプル・広く使われている⭐⭐⭐⭐algorithms 必須
express-jwtExpress 専用ミドルウェア⭐⭐⭐⭐Express 以外では使えない
passport-jwtPassport 認証戦略⭐⭐⭐Passport 依存
jwa低レベル署名プリミティブ新規プロジェクトで直接使用しない

💡 最終的なアドバイス

  • 新規プロジェクトでは、まず jose を検討してください。RFC 準拠で将来性があり、セキュリティ面でも堅牢です。
  • 既存の Express アプリで手早く JWT を導入したいなら、express-jwt が最速です。
  • Passport をすでに使っているなら、passport-jwt で統一するのが自然です。
  • jwa は内部ライブラリとして認識し、アプリケーションコードでは直接呼び出さないでください。

JWT の実装は「動けばいい」ではなく、「安全に動く」ことが最重要です。ライブラリ選びは、単なる好みではなく、セキュリティとメンテナンス性に直結する重要な判断です。

選び方: express-jwt vs jose vs jsonwebtoken vs jwa vs passport-jwt

  • express-jwt:

    express-jwt は Express アプリケーションで JWT 認証を素早く実装したい場合に最適です。リクエストヘッダーからトークンを自動で取り出し、検証結果を req.auth に格納するため、最小限のコードで保護されたエンドポイントを構築できます。ただし、Express 以外の環境では使用できない点に注意が必要です。

  • jose:

    jose は RFC 準拠でモダンな JWT 実装を求められる場合に選ぶべきです。ESM/CJS 両対応、TypeScript ファースト、JWK や公開鍵方式のサポートなど、将来を見据えた堅牢な設計が特徴です。フレームワーク非依存なので、Next.js API Routes や Cloudflare Workers など幅広い環境で利用できます。

  • jsonwebtoken:

    jsonwebtoken はシンプルで広く使われている JWT ライブラリです。学習コストが低く、多くのチュートリアルや既存コードで採用されているため、短期間で実装を完了させたいケースに向いています。ただし、verify() 時に algorithms オプションを明示的に指定しないとセキュリティリスクがあるため、注意が必要です。

  • jwa:

    jwa は JWT の署名アルゴリズム(JWA)部分だけを実装した低レベルライブラリです。通常はアプリケーションコードで直接使用するのではなく、他の JWT ライブラリの内部で利用されます。新規プロジェクトではこのパッケージを直接インポートすべきではなく、代わりに josejsonwebtoken を検討してください。

  • passport-jwt:

    passport-jwt は既に Passport 認証を採用しているプロジェクトで JWT 戦略を追加したい場合に適しています。Passport のエコシステムと統合され、複数の認証方法(例: ローカル、OAuth、JWT)を併用する複雑な認証フローを構築できます。ただし、Passport に依存するため、単独での使用は非効率です。

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.