auth0/express-jwt: JWT validation middleware for Express, and where it stops
connect/express middleware that validates a JsonWebToken (JWT) and set the req.user with the attributes
At a glance
- What is it?
- express-jwt is a small Express middleware that verifies a JSON Web Token and puts the decoded payload on the request. It handles verification, not login, not sessions, and not authorization decisions.
- Who is it for?
- Adopt express-jwt if you already issue tokens elsewhere and your Express routes only need the payload verified and attached to the request. Do not adopt it if you expect it to log users in, manage sessions, or decide what a user is allowed to do; the README leaves all of that to your own route code.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 97 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What express-jwt actually takes off your plate
The package describes itself in package.json as "JWT authentication middleware", but the scope is narrower than that phrase suggests. It wraps the jsonwebtoken verification function in an Express middleware, extracts a token from the request, verifies it, and assigns the decoded payload to a request property. The README states that the decoded JWT payload is available on the request via the auth property, and that requestProperty defaults to req.auth.
So the problem it solves is placement and plumbing. Without it you would write, in every protected route or router, the same sequence: read the Authorization header, strip the Bearer prefix, call jsonwebtoken verify with the right key and algorithm list, catch the error, and attach the result to the request. express-jwt turns that into one call you can mount at the router level. It is for teams that already have a token issuer (an identity provider, an internal auth service, another Express app) and need the consuming Express service to reject bad tokens before the handler runs.
It is not a login system. There is no endpoint in the package that checks a password or mints a token. The README's examples all assume a token arrives from somewhere else.
The verification path: token in, req.auth out
The data flow is short enough to hold in your head. A request reaches the middleware. A token getter runs, and by default it reads the Authorization header as an OAuth2 Bearer token, which the README calls the module's default behavior. The token is verified against the secret, and the algorithms list constrains which signing algorithms are acceptable. If verification succeeds, the payload lands on the request property, req.auth unless you change it. If it fails, the middleware responds with an error rather than calling next().
The options that shape this path are the interesting part. secret is required and may be a string, a Buffer, or a function. getToken replaces the default extraction, which the README suggests for tokens passed through a query parameter or a cookie. credentialsRequired set to false makes the middleware continue to the next handler when no token is present instead of failing, which is how you mount one middleware across a router that mixes public and private routes. onExpired handles expired tokens, and isRevoked lets you reject a token that verifies cryptographically but should no longer be honored.
The secret can also be a function, GetVerificationKey, receiving the request and an object with the JWT payload and headers, returning a Promise of the secret. The README shows two uses: picking a tenant secret by the iss claim, and secret rotation where the kid header selects the verification key. That is the design decision worth noting. Key selection is pushed into your code, which is flexible but means the correctness of multi-tenant or rotating-key setups is yours to prove, not the library's.
Installing express-jwt and protecting one route
The README gives a single install command. It pulls in jsonwebtoken and express-unless as dependencies, and the package ships compiled output plus types, with main pointing at dist/index.js and types at dist/index.d.ts.
npm install express-jwtThe basic usage example uses an HS256 shared secret and mounts the middleware on a single route. The destructured name matters: the export is expressjwt, aliased to jwt in the README's examples.
var { expressjwt: jwt } = require("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);
}
);After this, a request with a valid Bearer token reaches the handler with req.auth populated. The handler in the example then makes its own authorization decision by reading req.auth.admin and returning 401 when it is falsy. That is the pattern to internalize: express-jwt proves the token is authentic, your handler decides what the caller may do.
To apply it across a router instead of one route, the README mounts it with app.use under a path prefix, and to carve out exceptions it calls .unless on the middleware, which comes from express-unless.
app.use("/api", jwt({ secret: "shhhhhhared-secret", algorithms: ["HS256"] }));
app.use(
jwt({ secret: "shhhhhhared-secret", algorithms: ["HS256"] }).unless({ path: ["/token"] })
);The path value in unless accepts a string, a regexp, or an array of either, per the README.
The algorithms option is not optional in practice
The README states plainly that the algorithms parameter is required to prevent potential downgrade attacks when third party libraries are provided as secrets. It also warns against mixing symmetric and asymmetric algorithms such as HS256 and RS256 without further validation, because that can result in downgrade vulnerabilities. Treat this as the one setting you cannot omit or guess. If your issuer signs with RS256, list RS256 and nothing else.
The related advice in the README is to specify audience and issuer, which it calls highly recommended for security purposes. Those two claims bind the token to the API it was meant for and the party that issued it. A token that verifies against your key but was minted for a different audience is still a valid signature over the wrong claims, and audience checking is what catches that. The README notes that if the JWT carries an exp claim, it is checked.
For asymmetric setups the README reads a public key from disk into a Buffer and passes it as secret alongside an RS256 algorithms list. For base64 URL-encoded symmetric secrets it passes a Buffer with base64 encoding instead of a plain string. Both are one-line changes, and both are places where a mistake produces a runtime verification failure rather than a helpful error.
Where express-jwt is the wrong tool
The clearest limitation is that verifying a token is not the same as knowing whether its holder should still be trusted. Stateless verification means a token stays valid until exp passes. The README offers isRevoked as the escape hatch, a function receiving the request and the token and returning a Promise of a boolean, with an example that checks an (iss, jti) claim pair against a data module. That callback runs on every request, so revocation turns a stateless check into a lookup against whatever store backs it. If you need immediate revocation and cannot accept that lookup, this middleware is the wrong layer for the problem.
A second boundary is token location. The default is the Authorization header as a Bearer token. If your front end sends the token in a cookie, you must supply getToken and handle extraction yourself, including the null case. The README's example returns null when neither the header nor a query parameter yields a token, and pairs that with credentialsRequired: false.
A third case is anything beyond authentication. There is no role model, no scope parsing, no permission table. The README's own example pushes the admin check into the route handler. If you want declarative permissions at the middleware layer, you are building that on top of req.auth, not configuring it.
Finally, the README does not document rollback or migration guidance between major versions. The repository has a CHANGELOG.md and uses semantic-release, so version history exists, but the README itself does not walk you through upgrading.
express-jwt vs jsonwebtoken, and what the difference buys you
The most direct alternative is jsonwebtoken itself, which express-jwt depends on and wraps. The difference is not the cryptography; both ultimately call the same verify function. The difference is Express integration. With jsonwebtoken alone you write the middleware: pull the header, split on the space, handle the missing-token case, call verify, map errors to responses, and assign the payload. With express-jwt that plumbing is the package, plus the options it adds on top: getToken, credentialsRequired, requestProperty, isRevoked, onExpired, and the .unless helper from express-unless.
That means choosing express-jwt is mostly a bet that you want its conventions. If you already have a custom auth middleware, or you are not on Express, the wrapper adds a dependency without removing work. If you are on Express and mounting the same check across many routes, the unless helper and the router-level mount are the concrete savings.
A second comparison worth making is against doing authorization in the token itself versus in the handler. express-jwt enables the former by handing you the full payload, but it does not enforce it. The README example reads req.auth.admin inside the route. Any design where the middleware is expected to reject non-admins is a misreading of what the package does.
Maintenance, versioning and the MIT licence
The repository is not archived, and its last push was on 2026-06-25. Package version is 8.5.1. The toolchain visible in package.json is conventional: TypeScript compiled with tsc, mocha tests run through ts-node, eslint and prettier for formatting, husky for git hooks, commitlint for commit messages, and semantic-release for publishing. There is a CHANGELOG.md at the repository root, and the release process appears to derive versions from conventional commit messages.
For upgrade cost, that setup suggests predictable semver releases rather than ad hoc ones, but the README does not describe a deprecation policy or a support window. The engines field declares node >= 8.0.0, which is a floor, not a statement about which Node versions are tested. The devDependencies pin @types/node to ^18.19.130, so the test environment is modern Node even though the declared floor is old.
The licence is MIT, declared in package.json and present as a LICENSE file at the repository root. MIT is permissive: it allows use, modification and redistribution with the copyright notice and permission notice retained. That is a factual description of the licence text, not legal advice; if your organization has a policy on dependency licences, run it through that process. One practical note for bundlers and auditors: the package ships only README.md and dist, so the source under src/ is not part of the published artifact.
Editorial conclusion
Adopt express-jwt if you already issue tokens elsewhere and your Express routes only need the payload verified and attached to the request. Do not adopt it if you expect it to log users in, manage sessions, or decide what a user is allowed to do; the README leaves all of that to your own route code. Before wiring it into a service, verify three things: that you always pass the algorithms array, that your secret source and audience and issuer values match the tokens you actually mint, and whether your token location is the Authorization header or something you must extract with getToken.
Frequently asked questions
What is express-jwt and what does it do?
It is Express middleware that validates a JSON Web Token using the jsonwebtoken module and places the decoded payload on the request object, by default at req.auth. It extracts the token from the Authorization header as an OAuth2 Bearer token unless you supply a getToken function.
How is express-jwt different from using jsonwebtoken directly?
express-jwt depends on jsonwebtoken and wraps its verify function in Express middleware. The README states that the module provides Express middleware for validating JWTs through jsonwebtoken, so the added value is the Express integration plus options such as getToken, credentialsRequired, isRevoked, onExpired and the .unless helper.
How do I set up express-jwt in an Express app?
Install it with npm install express-jwt, then mount it with jwt({ secret: "shhhhhhared-secret", algorithms: ["HS256"] }) on a route or with app.use under a path prefix. The algorithms option is required in the README's examples to prevent potential downgrade attacks.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/auth0-express-jwt)