express-jwt、jose、jsonwebtoken、jwa、passport-jwt はすべて Node.js 環境で JWT(JSON Web Token)を扱うための npm パッケージですが、それぞれ役割と抽象度が大きく異なります。jose と jsonwebtoken は JWT の署名・検証といったコア機能を提供する汎用ライブラリです。一方、express-jwt は Express 専用のミドルウェア、passport-jwt は Passport 認証戦略として動作し、フレームワークに強く依存します。jwa は低レベルの署名アルゴリズム実装に特化しており、通常はアプリケーションコードで直接使用されません。
JWT(JSON Web Token)は認証や情報交換に広く使われる仕組みですが、Node.js エコシステムには複数の実装があります。それぞれ目的や抽象度が異なり、選択を誤るとセキュリティリスクやメンテナンス負荷につながります。ここでは、express-jwt、jose、jsonwebtoken、jwa、passport-jwt の5つのパッケージを、実際の開発現場の観点から深く比較します。
joseRFC準拠の最新実装で、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-jwtExpress ミドルウェア専用のライブラリ。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-jwtPassport 認証戦略として動作します。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-jwt と passport-jwt は、それぞれ Express と Passport という特定のフレームワークに強く依存しています。これらを使う場合、アプリケーションがそのエコシステムに縛られます。jose と jsonwebtoken はフレームワーク非依存で、どこでも使えます。例えば Next.js API Routes や Cloudflare Workers など、Express 以外の環境でも問題なく動作します。jose と jsonwebtoken は高レベル 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 準拠度が高いです。express-jwtreq.auth で即座にユーザー情報を参照可能。app.use(expressJwt({ secret: process.env.JWT_SECRET, algorithms: ['HS256'] }));
passport-jwtjosejsonwebtokenalgorithms オプションを忘れないように注意。jwa| パッケージ | 用途 | 推奨度 | 注意点 |
|---|---|---|---|
jose | モダン・RFC準拠・フレームワーク非依存 | ⭐⭐⭐⭐⭐ | 学習曲線やや高め |
jsonwebtoken | シンプル・広く使われている | ⭐⭐⭐⭐ | algorithms 必須 |
express-jwt | Express 専用ミドルウェア | ⭐⭐⭐⭐ | Express 以外では使えない |
passport-jwt | Passport 認証戦略 | ⭐⭐⭐ | Passport 依存 |
jwa | 低レベル署名プリミティブ | ⭐ | 新規プロジェクトで直接使用しない |
jose を検討してください。RFC 準拠で将来性があり、セキュリティ面でも堅牢です。express-jwt が最速です。passport-jwt で統一するのが自然です。jwa は内部ライブラリとして認識し、アプリケーションコードでは直接呼び出さないでください。JWT の実装は「動けばいい」ではなく、「安全に動く」ことが最重要です。ライブラリ選びは、単なる好みではなく、セキュリティとメンテナンス性に直結する重要な判断です。
express-jwt は Express アプリケーションで JWT 認証を素早く実装したい場合に最適です。リクエストヘッダーからトークンを自動で取り出し、検証結果を req.auth に格納するため、最小限のコードで保護されたエンドポイントを構築できます。ただし、Express 以外の環境では使用できない点に注意が必要です。
jose は RFC 準拠でモダンな JWT 実装を求められる場合に選ぶべきです。ESM/CJS 両対応、TypeScript ファースト、JWK や公開鍵方式のサポートなど、将来を見据えた堅牢な設計が特徴です。フレームワーク非依存なので、Next.js API Routes や Cloudflare Workers など幅広い環境で利用できます。
jsonwebtoken はシンプルで広く使われている JWT ライブラリです。学習コストが低く、多くのチュートリアルや既存コードで採用されているため、短期間で実装を完了させたいケースに向いています。ただし、verify() 時に algorithms オプションを明示的に指定しないとセキュリティリスクがあるため、注意が必要です。
jwa は JWT の署名アルゴリズム(JWA)部分だけを実装した低レベルライブラリです。通常はアプリケーションコードで直接使用するのではなく、他の JWT ライブラリの内部で利用されます。新規プロジェクトではこのパッケージを直接インポートすべきではなく、代わりに jose や jsonwebtoken を検討してください。
passport-jwt は既に Passport 認証を採用しているプロジェクトで JWT 戦略を追加したい場合に適しています。Passport のエコシステムと統合され、複数の認証方法(例: ローカル、OAuth、JWT)を併用する複雑な認証フローを構築できます。ただし、Passport に依存するため、単独での使用は非効率です。
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.
$ npm install express-jwt
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.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;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
Authorizationheader as an OAuth2 Bearer token.
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']
});
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
.unlesssyntax 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"] });
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;
},
})
);
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);
}
);
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;
};
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);
}
);
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;
},,
})
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,
})
);
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);
}
);
secret function had (req, header, payload, cb), now it can return a promise and receives (req, token). token has header and payload.isRevoked function had (req, payload, cb), now it can return a promise and receives (req, token). token has header and payload.$ npm install
$ npm test
Check them out here
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.
This project is licensed under the MIT license. See the LICENSE file for more info.