auth0/node-jsonwebtoken: signing and verifying JWTs in Node.js
JsonWebToken implementation for node.js http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html
At a glance
- What is it?
- The jsonwebtoken npm package is the long-standing Node.js implementation of RFC 7519, built on node-jws. It is small, synchronous by default, and opinionated about claims, and the current release line is 9.0.3.
- Who is it for?
- Adopt jsonwebtoken if you are already on Node.js and want a small HMAC or RSA signer with explicit claim handling. Do not adopt it if you need JWK set rotation, EdDSA, or browser support, because the README and package.json describe an HMAC and RSA/ECDSA library that runs on Node.
- 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 JavaScript, 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 jsonwebtoken solves, and for whom
JSON Web Tokens are a compact way to carry signed claims between two parties. The format is defined in RFC 7519, and auth0/node-jsonwebtoken is a Node.js implementation of it. The package description in package.json reads "JSON Web Token implementation (symmetric and asymmetric)", which is the whole scope in one line.
The intended user is a backend developer who needs to mint a token after a login and check it on a later request. The README's own framing is narrow: the project was developed against draft-ietf-oauth-json-web-token-08 and makes use of node-jws. That draft lineage matters, because it explains why the API is claim-centric rather than algorithm-centric. You pass a payload, you pass a key, and the library fills in iat, exp, nbf, aud, iss, sub and jti for you.
This is not an identity provider, not a session store, and not a key management service. It signs and verifies. Everything around that, such as where the key lives, how it rotates, and what you do with the decoded claims, is your problem. The README is explicit that a decoded payload from an untrusted source "should be treated like any other user input".
That boundary is the honest way to read the project. It is a primitive, and it behaves like one.
The sign and verify mechanism behind the API
The package exposes three main entry points, mirrored by three files in the repository root: sign.js, verify.js and decode.js. Those three files, plus lib, are the entire published surface, according to the files array in package.json. The heavy lifting is delegated to jws, which is listed as a runtime dependency.
jwt.sign(payload, secretOrPrivateKey, [options, callback]) returns a token string synchronously, or calls a callback if you supply one. The README states that a payload can be an object literal, a buffer, or a string representing valid JSON. The important caveat is printed in the README itself: exp or any other claim is only set if the payload is an object literal, and buffer or string payloads are not checked for JSON validity. If you pass a raw string, you are on your own for claim correctness.
When the payload is not a buffer or a string, it is coerced with JSON.stringify. The library then adds an iat claim by default unless noTimestamp is set, and computes exp and nbf from options.expiresIn and options.notBefore.
jwt.verify(token, secretOrPublicKey, [options, callback]) runs the reverse. Without a callback it is synchronous and throws on failure; with a callback it reports the error through the callback. Verification checks the signature plus optional expiration, audience, and issuer.
The algorithm is HS256 by default. That default is worth pausing on. A library that defaults to a symmetric algorithm will happily accept a shared secret for a use case where an asymmetric key pair would be safer, and nothing in the API forces you to think about it.
Installing jsonwebtoken and signing a first token
The README gives one install command. It pulls the package from the npm registry, and the package.json engines field requires npm >=6 and node >=12.
$ npm install jsonwebtokenAfter that, the shortest working example in the README signs an HMAC SHA256 token with a shared secret. The return value is the token string itself.
var jwt = require('jsonwebtoken');
var token = jwt.sign({ foo: 'bar' }, 'shhhhh');The same call with an expiration uses the expiresIn option. Note the README's warning about units: a numeric value is seconds, but a string without time units is milliseconds, so "120" means 120ms, not 120 seconds.
jwt.sign({
data: 'foobar'
}, 'secret', { expiresIn: '1h' });For asymmetric signing, read the key from disk and name the algorithm. The README shows RS256, and states that RSA private keys below a 2048-bit modulus are rejected unless allowInsecureKeySizes is true.
var privateKey = fs.readFileSync('private.key');
var token = jwt.sign({ foo: 'bar' }, privateKey, { algorithm: 'RS256' });Verification is the mirror call. Pass the token, the secret or public key, and a callback if you want the error delivered rather than thrown. The callback receives the decoded payload when the signature and any requested expiration, audience, or issuer checks pass.
Claims you cannot put in two places at once
The README lists expiresIn, notBefore, audience, subject, issuer and jwtid as options, then states that there are no default values for expiresIn, notBefore, audience, subject or issuer. Those claims can instead be placed directly in the payload as exp, nbf, aud, sub and iss. The constraint is absolute: you cannot include them in both places.
This is a design decision with real consequences. A helper function that merges a caller-supplied payload with a library-level expiresIn option will throw or misbehave the moment the caller passes an exp field. The library refuses to guess which one you meant.
Time handling is the other sharp edge. exp, nbf and iat are NumericDate values, meaning seconds since the epoch. The README quotes the RFC definition at length for a reason: developers routinely pass Date.now() milliseconds and produce tokens that are valid for tens of thousands of years. The README's own example divides by 1000 and floors the result.
There is a related subtlety in the iat behavior. Generated tokens include an iat claim unless noTimestamp is specified, and if iat is already present in the payload, the library uses that value instead of the real timestamp when computing exp from an expiresIn timespan. Backdating is therefore possible by hand, as the README's 30-second backdate example shows, and the same mechanism can produce an exp that is wrong if you set iat carelessly.
The mutatePayload option is the escape hatch for code that needs a reference to the payload object after claims have been applied but before encoding. It modifies your object in place, which is exactly as dangerous as it sounds.
Where jsonwebtoken is the wrong tool
The most common failure mode is treating verification as authentication. jwt.verify returns a decoded payload when the signature and the optional claims check out. It does not tell you whether the user is still allowed to do the thing. A token signed with a one-hour expiry is valid for one hour regardless of what happened to the account in between, because revocation is not part of the token format and not part of this library.
A second limitation is key handling. The README describes secretOrPrivateKey as a string, buffer, object, or KeyObject, and mentions an object with key and passphrase for encrypted private keys. There is no key set, no JWK fetching, and no automatic rotation. If your architecture depends on pulling public keys from a JWKS endpoint and refreshing them, this package does not do that for you.
The third is the runtime. The engines field requires Node >=12, and the package is published for Node. If you need the same verification logic in a browser or at the edge, this is not the library for that.
Finally, consider the dependency list before adopting it in a size-sensitive context. Alongside jws and ms, package.json pulls in six separate lodash micro-packages for type checks, plus semver. None of them are large, but the count is higher than the API surface suggests, and each one is a supply chain node you now track.
jsonwebtoken versus jose: two different jobs
The natural comparison is jose, which is what people search for alongside this package. The difference is architectural rather than a matter of quality.
jose is built on the Web Crypto API and targets multiple runtimes, including browsers and edge workers. node-jsonwebtoken is built on Node's crypto module through node-jws and targets Node only, as the engines field confirms. If your token verification has to run in a Cloudflare Worker or in a React client, that single fact decides the question.
The second difference is scope. jose covers the broader JOSE family, including JWK and JWKS handling, which is what you need when keys rotate and you fetch them from an endpoint. jsonwebtoken expects you to hand it a key you already have. That is a smaller surface and a smaller set of decisions, and for a service that holds one HMAC secret or one RSA key pair, it is often the right amount of library.
The third difference is API shape. jsonwebtoken is promise-optional: synchronous by default, callback if you pass one. jose is promise-based throughout. Code that already lives in an async Node service will not notice either way, but code that wants a plain synchronous verify will find jsonwebtoken more direct.
Neither is a drop-in for the other. Choosing between them means deciding whether you want a Node-specific primitive or a cross-runtime JOSE toolkit.
Maintenance, licence, and the cost of the next major version
The repository is not archived, and the last push was on 2026-06-25. The current published version is 9.0.3. The README links migration notes for v8 to v9 and for v7 to v8 as separate wiki pages, which tells you the project has a history of breaking changes between majors and documents them rather than hiding them.
The repository uses conventional commits, commitlint, husky and semantic-release, so releases are cut from commit messages. Practically, that means an upgrade path exists and is automated, but it also means the changelog is generated rather than curated by hand.
Test coverage is enforced rather than aspirational. The nyc block in package.json sets check-coverage to true with thresholds of 95 percent for lines, statements and branches, and 100 percent for functions. That is a meaningful constraint on contributions, and it is the strongest signal in the repository about how the maintainers treat correctness.
The licence is MIT, per both the LICENSE file and the license field in package.json. MIT is permissive: it allows commercial and closed-source use, and it comes with no warranty. That is a factual description of the terms, not legal advice, and if your organisation has a policy about attribution or about dependency licences in distributed binaries, the LICENSE file is the document to read.
The upgrade cost is the real maintenance question. Because this library sits on the authentication path, a major version bump is not a routine dependency update. The migration notes exist precisely because v8 to v9 changed behavior that applications depended on. Budget for reading them, not for running npm update.
Editorial conclusion
Adopt jsonwebtoken if you are already on Node.js and want a small HMAC or RSA signer with explicit claim handling. Do not adopt it if you need JWK set rotation, EdDSA, or browser support, because the README and package.json describe an HMAC and RSA/ECDSA library that runs on Node. Verify first that your runtime satisfies engines.node >=12, that your RSA keys are at least 2048 bits unless you knowingly set allowInsecureKeySizes, and that you have read the v8 to v9 migration notes before upgrading.
Frequently asked questions
How do I install jsonwebtoken?
Run npm install jsonwebtoken. The package requires npm >=6 and node >=12 according to its engines field, and it is published for Node.js rather than the browser.
How does jsonwebtoken verify a token?
jwt.verify(token, secretOrPublicKey, [options, callback]) checks the signature and any optional expiration, audience or issuer. Without a callback it runs synchronously and throws on failure; with a callback the error is passed to the callback instead.
How do I decode a JSON web token with jsonwebtoken?
The repository root contains a decode.js file, which backs the decode entry point of the package. The README's verification section is the documented path for reading claims, and it warns that a payload from an untrusted source should be treated like any other user input.
What are the three parts of a JWT that jsonwebtoken produces?
A JWT is a signed, compact string, and jsonwebtoken builds it through node-jws. The README does not spell out the three-part structure, but it does document the header, which can be customized through the options.header object, and the payload, which carries the claims.
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-node-jsonwebtoken)