express-jwt vs express-jwt-authz vs express-jwt-permissions vs jsonwebtoken vs passport-jwt
Node.js における JWT 認証ミドルウェアの選定と実装
express-jwtexpress-jwt-authzexpress-jwt-permissionsjsonwebtokenpassport-jwt類似パッケージ:

Node.js における JWT 認証ミドルウェアの選定と実装

express-jwtexpress-jwt-authzexpress-jwt-permissionsjsonwebtokenpassport-jwt は、Node.js アプリケーションで JWT(JSON Web Token)ベースの認証・認可を実装するためのパッケージ群です。jsonwebtoken は JWT の生成・検証を行う低レベルライブラリで、他のパッケージはこれを基に Express ミドルウェアとして機能します。express-jwt は Express ルート保護の基本的なミドルウェア、express-jwt-authz はスコープベースの認可、express-jwt-permissions はロール・パーミッションベースのきめ細かいアクセス制御、passport-jwt は Passport.js エコシステムとの統合を提供します。

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

3 年

GitHub Starsランキング

統計詳細

パッケージ
ダウンロード数
Stars
サイズ
Issues
公開日時
ライセンス
express-jwt04,51228.5 kB641年前MIT
express-jwt-authz0987.75 kB6-MIT
express-jwt-permissions052219.9 kB9-MIT
jsonwebtoken018,17243.4 kB2006ヶ月前MIT
passport-jwt01,98252 kB43-MIT

Node.js JWT 認証パッケージ:express-jwt、jsonwebtoken、passport-jwt 徹底比較

Node.js で JWT 認証を実装する際、どのパッケージを選ぶべきかはプロジェクトの要件によって大きく異なります。express-jwtexpress-jwt-authzexpress-jwt-permissionsjsonwebtokenpassport-jwt はそれぞれ異なる役割と特徴を持っています。実際の開発現場で直面する課題に沿って、各パッケージの違いを解説します。

⚠️ 重要な注意点:express-jwt の非推奨状態

express-jwt は Auth0 によって非推奨(deprecated)となっています。 新規プロジェクトでの使用は避け、代替案を検討すべきです。この比較では技術的な理解のために解説しますが、本番環境では passport-jwtjsonwebtoken を直接使用するカスタム実装を推奨します。

🔑 基本機能:JWT の検証とトークン処理

jsonwebtoken は JWT 操作の低レベルライブラリです。トークンの署名・検証・デコードを直接行います。

const jwt = require('jsonwebtoken');

// トークン検証
const decoded = jwt.verify(token, process.env.JWT_SECRET, {
  algorithms: ['HS256']
});

// トークン生成
const newToken = jwt.sign(
  { userId: 123, role: 'admin' },
  process.env.JWT_SECRET,
  { expiresIn: '1h' }
);

express-jwt は Express ミドルウェアとして JWT 検証を自動化します(非推奨)。

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

app.use(auth({
  secret: process.env.JWT_SECRET,
  algorithms: ['HS256'],
  requestProperty: 'auth'
}));

// ルート内で検証済みデータにアクセス
app.get('/profile', (req, res) => {
  const userId = req.auth.sub;
});

passport-jwt は Passport.js の戦略として JWT 認証を実装します。

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

passport.use(new JwtStrategy({
  jwtFromRequest: (req) => {
    const authHeader = req.headers.authorization;
    return authHeader ? authHeader.replace('Bearer ', '') : null;
  },
  secretOrKey: process.env.JWT_SECRET
}, (payload, done) => {
  // ユーザー検索ロジック
  return done(null, payload);
}));

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

🛡️ 認可制御:スコープとパーミッション

express-jwt-authz は JWT のスコープ(scope)クレームに基づきアクセス制御を行います。

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

// express-jwt ミドルウェア後に使用
app.use(auth({ secret: process.env.JWT_SECRET }));

// スコープチェック
app.get('/admin',
  authorization(['read:users', 'write:users']),
  (req, res) => {
    res.json({ message: 'Admin access granted' });
  }
);

express-jwt-permissions はより柔軟なロール・パーミッションベースの制御を提供します。

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

const guard = permissions({
  requestProperty: 'auth',
  permissionsProperty: 'permissions'
});

app.get('/dashboard',
  guard(['dashboard:read', 'dashboard:write']),
  (req, res) => {
    res.json({ data: 'Protected content' });
  }
);

jsonwebtoken 単体では認可制御機能はありません。カスタムミドルウェアで実装する必要があります。

// カスタム認可ミドルウェア
function checkScope(requiredScopes) {
  return (req, res, next) => {
    const token = req.headers.authorization?.replace('Bearer ', '');
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    
    const hasScope = requiredScopes.every(scope =>
      decoded.scope?.includes(scope)
    );
    
    if (!hasScope) {
      return res.status(403).json({ error: 'Insufficient scope' });
    }
    next();
  };
}

app.get('/admin', checkScope(['admin:read']), (req, res) => {
  res.json({ message: 'Access granted' });
});

🔄 エラーハンドリングとカスタマイズ性

express-jwt はビルトインのエラーハンドリングを提供しますが、カスタマイズに限界があります。

app.use(auth({
  secret: process.env.JWT_SECRET
}).unless({ path: ['/public'] }));

// エラーハンドリングミドルウェア
app.use((err, req, res, next) => {
  if (err.name === 'UnauthorizedError') {
    res.status(401).json({ error: 'Invalid token' });
  }
});

passport-jwt は Passport のエラーハンドリングフローに従います。

app.use(passport.authenticate('jwt', {
  session: false,
  failWithError: true
}));

// Passport エラーハンドリング
app.use((err, req, res, next) => {
  if (err.message === 'Missing authentication token') {
    res.status(401).json({ error: 'Token required' });
  }
});

jsonwebtoken はエラーをスローするため、try-catch で囲む必要があります。

try {
  const decoded = jwt.verify(token, process.env.JWT_SECRET);
  next();
} catch (err) {
  if (err.name === 'TokenExpiredError') {
    res.status(401).json({ error: 'Token expired' });
  } else if (err.name === 'JsonWebTokenError') {
    res.status(401).json({ error: 'Invalid token' });
  }
}

📦 複数認証戦略の統合

passport-jwt の最大の強みは、複数の認証戦略を統一インターフェースで扱える点です。

const { Strategy: LocalStrategy } = require('passport-local');
const { Strategy: JwtStrategy } = require('passport-jwt');

// ローカル認証(ログイン)
passport.use(new LocalStrategy((username, password, done) => {
  // ユーザー検証
}));

// JWT 認証(保護ルート)
passport.use(new JwtStrategy({
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  secretOrKey: process.env.JWT_SECRET
}, (payload, done) => {
  done(null, payload);
}));

// ログインエンドポイント
app.post('/login', passport.authenticate('local'), (req, res) => {
  const token = jwt.sign({ userId: req.user.id }, process.env.JWT_SECRET);
  res.json({ token });
});

// 保護エンドポイント
app.get('/profile', passport.authenticate('jwt', { session: false }), (req, res) => {
  res.json(req.user);
});

express-jwtjsonwebtoken は単一の認証方法に特化しており、複数戦略の統合には追加コードが必要です。

🎯 実ユースケース別推奨

ケース 1:シンプルな API 保護

Express アプリで JWT 検証のみが必要な場合:

  • 推奨: jsonwebtoken + カスタムミドルウェア
  • 非推奨: express-jwt(非推奨パッケージのため)
// カスタム JWT ミドルウェア
function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];
  
  if (!token) return res.sendStatus(401);
  
  jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
    if (err) return res.sendStatus(403);
    req.user = user;
    next();
  });
}

app.get('/protected', authenticateToken, (req, res) => {
  res.json({ data: 'Protected data' });
});

ケース 2:Passport エコシステム使用時

既に Passport.js を使用している、または複数認証戦略が必要な場合:

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

ケース 3:スコープベースの認可

Auth0 などの ID プロバイダーと連携し、スコープ管理が必要な場合:

  • 推奨: express-jwt-authz(express-jwt と併用)
  • ⚠️ 注意: express-jwt の非推奨を考慮し、カスタム実装も検討
app.get('/admin',
  authorization(['read:users', 'write:users']),
  (req, res) => {
    res.json({ message: 'Admin access' });
  }
);

ケース 4:きめ細かいパーミッション制御

ロールベースアクセス制御(RBAC)や詳細なパーミッション管理が必要な場合:

  • 推奨: express-jwt-permissions
const guard = permissions({
  requestProperty: 'auth',
  permissionsProperty: 'permissions'
});

app.delete('/users/:id',
  guard(['users:delete']),
  (req, res) => {
    // 削除処理
  }
);

📊 機能比較サマリー

機能express-jwtexpress-jwt-authzexpress-jwt-permissionsjsonwebtokenpassport-jwt
JWT 検証❌(express-jwt が必要)❌(express-jwt が必要)
トークン生成
スコープチェック❌(カスタム)❌(カスタム)
パーミッション制御❌(カスタム)❌(カスタム)
複数認証戦略
Express 依存✅(推奨)
保守状況⚠️ 非推奨⚠️ 非推奨依存⚠️ 非推奨依存✅ 維持✅ 維持

💡 最終推奨

新規プロジェクトでの推奨構成:

  1. 基本認証のみ: jsonwebtoken + カスタムミドルウェア

    • 依存関係を最小限に抑え、コントロール性を最大化
  2. Passport 使用時: passport-jwt

    • 複数認証戦略の統合が必要な場合に最適
  3. 認可制御が必要: カスタム実装または express-jwt-permissions

    • express-jwt の非推奨を考慮し、長期的な保守性を重視

避けるべき構成:

  • express-jwt を新規プロジェクトで使用(非推奨のため)
  • express-jwt-authzexpress-jwt-permissions を express-jwt なしで使用(動作しません)

重要なポイント: JWT 認証パッケージの選定は、単なる機能比較ではなく、プロジェクトの長期的な保守性、チームの技術スタック、セキュリティ要件を総合的に考慮して決定する必要があります。jsonwebtoken を基盤としたカスタム実装が、現代の Node.js アプリケーションでは最も柔軟で保守性の高い選択肢となります。

選び方: express-jwt vs express-jwt-authz vs express-jwt-permissions vs jsonwebtoken vs passport-jwt

  • express-jwt:

    express-jwt を選ぶのは、Express アプリでシンプルに JWT 検証を行いたい場合です。ルートレベルでトークンを検証し、有効な場合にのみリクエストを通過させたい時に最適です。ただし、Auth0 によって非推奨(deprecated)となっているため、新規プロジェクトでは代替案の検討を推奨します。

  • express-jwt-authz:

    express-jwt-authz は、JWT のスコープ(scope)クレームに基づいてアクセス制御を行いたい場合に使用します。特定の API エンドポイントに必要なスコープを定義し、トークンにそのスコープが含まれているかチェックします。express-jwt と組み合わせて使用する必要があります。

  • express-jwt-permissions:

    express-jwt-permissions は、より複雑なロール・パーミッションベースの認可が必要な場合に適しています。トークン内のカスタムクレームを元に、きめ細かいアクセス制御ポリシーを定義できます。express-jwt との併用が前提となっています。

  • jsonwebtoken:

    jsonwebtoken は JWT の生成・署名・検証を行うコアライブラリです。他のパッケージの基盤となっており、カスタム認証フローを構築する場合や、ミドルウェアに依存しない実装が必要な時に直接使用します。認証ミドルウェアではなく、JWT 操作そのものが必要な場合に選択します。

  • passport-jwt:

    passport-jwt を選ぶのは、Passport.js エコシステムを既に使用している場合、または複数の認証戦略(JWT、OAuth、ローカルなど)を統一されたインターフェースで管理したい場合です。Express 以外のフレームワークとの親和性も高く、柔軟な認証フロー構築が可能です。

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.