hapi-auth-jwt2: JSON Web Token authentication for Hapi.js
:lock: Secure Hapi.js authentication plugin using JSON Web Tokens (JWT) in Headers, URL or Cookies
At a glance
- What is it?
- hapi-auth-jwt2 is a Hapi.js authentication scheme that validates JSON Web Tokens, with the token accepted in a header, a URL query parameter or a cookie. The application supplies its own validation function, so the plugin verifies the signature and hands the decoded payload to code you own.
- Who is it for?
- hapi-auth-jwt2 suits a Hapi.js service that already issues JSON Web Tokens and wants the standard header based flow with a validation hook it controls, since the plugin keeps the signature check separate from your own user checks and supports rotating keys through a lookup function.
- Can I use it commercially?
- Yes. ISC 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 5 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 October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A scheme that verifies and then asks you
hapi-auth-jwt2 describes itself as the authentication scheme and plugin for Hapi.js applications using JSON Web Tokens, and the important word in that sentence is scheme.
A Hapi scheme is not a middleware and it is not an identity provider. It is the thing Hapi calls when a route asks for authentication. This plugin's job is to find a token, verify its signature, decode it, and hand the result to a function you wrote, which then decides whether to let the request through.
That split is visible in the example server, where the plugin is registered on the server and then a strategy is defined with a key and a validate function.
const server = new Hapi.server({ port: 8000 });
// include our module here ↓↓, for example, require('hapi-auth-jwt2')The comment in the example is worth noticing because it marks the one line you change when you install the package properly, since the example itself requires the module from a relative path into the library folder.
The repository also links two introductory guides from the same authors, one on JSON Web Tokens themselves and one on Hapi.js, which tells you the audience for this plugin is people who are new to at least one of the two ideas.
The validation function is where your rules live
The validate function is the required part of the configuration, and its signature tells you what the plugin passes in.
// bring your own validation function
const validate = async function (decoded, request, h) {
// do your checks to see if the person is valid
if (!people[decoded.id]) {
return { isValid: false };
}
else {
return { isValid: true };
}
};The three arguments are the decoded and verified token, the original request from the client, and the response toolkit. The return value has three fields. isValid says whether the token is acceptable, credentials is optional and lets you set alternative credentials instead of the decoded token, and response is optional for the cases where a rejection needs to carry a specific payload.
The example is deliberately trivial, checking that the decoded id exists in a hard coded people object. In a real application this is where you would look the user up, check a claim, or reject a token issued before a password change.
The plugin's philosophy is visible in the design: it verifies the cryptography and stops there. It does not know what a user is, and it does not want to. That means the security of your application depends on the checks you write, which is a feature when you have requirements the plugin could not know about, and a responsibility you cannot delegate to the dependency.
Key configuration and dynamic key lookup
The key option is required unless you supply a custom verify function, and the README describes it in two forms.
The first is the secret key, or an array of potential keys, used to check the signature of the token. The array form is the simple version of key rotation: try each key until one verifies.
The second is a key lookup function with the signature of an asynchronous function taking the decoded token, and it returns an object with the key and optional extra information. The decoded token passed to it is described as decoded but unverified, which is exactly what you need in order to decide which key to use, since the key identifier is usually a claim in the token itself.
The extra information field is the interesting part of the design. It lets the lookup carry information forward into the validation step, where it is accessible through the request plugins object under the plugin's own key. So a lookup that resolves a tenant from a claim can hand the tenant to the validate function without the two having to share state.
When the lookup fails, it throws a Boom error, and the README links an example implementation of a dynamic key server together with its test, which is the practical reference for getting that pattern right.
The stated reason for a lookup over a static list is the one that matters in practice: it lets you change where keys live without redeploying the application, and it lets you look up per tenant.
Where the token is allowed to travel
The plugin's description says it accepts JSON Web Tokens in headers, in a URL or in cookies, and the demonstration covers all three placements.
The header form is the standard one, and the demo shows a request that supplies an Authorization header carrying the token, and a route that echoes the header back so you can see what arrived.
The URL form passes the token as a query parameter, and the README shows a browser URL with the token in a token parameter. That is the pattern you use when the client is a browser redirect that cannot set a header, such as a download link that needs to be authorised.
The cookie form is the third, and it is the one the cookie dependency in the package manifest is there for.
The URL option deserves a warning, and it is a general one rather than a criticism of this plugin. A token in a query string ends up in browser history, in server access logs and in referrer headers, and those are places a bearer token should not be. The demo uses it because it is showing the mechanism, and because the README already includes a full token value in the page for you to copy.
The cookie option has the mirror image problem, since cookies are sent automatically by the browser, which makes them convenient and makes them a cross site request forgery risk unless the cookie flags are set properly. The plugin handles the token, not your cookie policy.
Running the demo
The quick demo is a clone and a start.
npm install && node example/server.jsThe example directory in the repository holds two servers, one of them a fuller version and one named simple, so you can start with the smaller one.
Then the demo walks through the two routes with curl. The public route is requested first.
curl -v http://localhost:8000/And then the restricted route without a token, where the README says to expect a 401 error, and notes that both the curl request and a visit in a web browser will be blocked with a 401 Unauthorized.
curl -v http://localhost:8000/restrictedAnd finally with the token in the Authorization header.
curl -v -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwibmFtZSI6IkFudGhvbnkgVmFsaWQgVXNlciIsImlhdCI6MTQyNTQ3MzUzNX0.KA68l60mjiC8EXaC2odnjFwdIDxE__iDu5RwLdN1F2A" \
http://localhost:8000/restrictedThe token in that example is a fixed string with a fixed secret, which is exactly what the example server uses as its key, and the example even comments on it with a warning never to share your secret key. The token in the documentation is therefore a teaching artefact and nothing else.
The final step in the README is the one that matters for real use: write your own validate with whatever checks you want to perform on the decoded token before allowing the visitor to proceed.
Versions, dependencies and the project layout
The release history is short and the reasons are informative.
The tags are v11.0.0 from 2025-01-28, whose description says it updates the release to conform to the latest ESLint requirements, v10.6.0 from 2024-06-04, which handles an error object passed into the raise error path, and v10.5.0 from 2023-12-10, which puts the exception and the decoded token into the error context.
That middle entry is a security-relevant improvement. Putting the decoded token and the exception into the error context is what lets a failed validation be debugged without turning the error into an information leak.
The runtime dependencies are three: the Hapi Boom error library, the cookie library and the jsonwebtoken library, which is the actual token verification and decoding. Everything else is development tooling, with ESLint 9 in its flat configuration format, nyc for coverage, a type checking tool for package exports, and pre-commit hooks.
The badges state the compatibility targets: Hapi 21.1.0 and Node 18, 20 and 21. There is also a Snyk badge for known vulnerabilities and a Codecov badge, plus an inline documentation badge, which suggests the API documentation is generated from the source comments.
The repository layout is the usual for a package of this kind, with the implementation in a lib directory, the examples in an example directory, the tests in a test directory, a changelog, a contributing guide, and a Chinese translation of the readme alongside the English one.
Licence, maintenance and what to check
The licence is ISC, which is a small permissive licence, and the last push to the repository was on 2026-10-18.
The project is the kind of dependency that quietly becomes load bearing. The version line is in double digits, the API is stable enough that people have not had to rewrite their auth code, and the releases are infrequent because there is little to change.
Three checks before you rely on it.
The Hapi and Node versions your application runs. The badges name Hapi 21.1.0 and Node 18, 20 and 21, and an older Hapi major is not going to work with the scheme API this plugin registers against.
Where your secret keys live. The key option accepts a static key, an array of keys, or a lookup function, and the third is the one that survives a rotation without a redeploy. If you use the static form with a key checked into a repository, the plugin is doing its job and your deployment is not.
And what your validate function actually checks. The plugin verifies the signature and gives you a decoded token. If your validation only checks that a user id exists, then a token issued before a password reset still works until the user record changes. The plugin cannot make that decision for you, which is exactly why the example function is so short and why the README tells you to write your own.
Editorial conclusion
hapi-auth-jwt2 suits a Hapi.js service that already issues JSON Web Tokens and wants the standard header based flow with a validation hook it controls, since the plugin keeps the signature check separate from your own user checks and supports rotating keys through a lookup function. It is the wrong choice if you want an opinionated identity provider, because the plugin deliberately does not decide who a user is, and it is the wrong choice if you are on a Hapi version or Node version outside the supported range. Verify first that the secret key management matches your deployment, that you understand the token in URL option before enabling it for anything sensitive, and that your Hapi version is covered by the plugin's peer expectations.
Frequently asked questions
What is hapi-auth-jwt2?
It is a Hapi.js authentication scheme and plugin that validates JSON Web Tokens, accepting the token in a header, in a URL query parameter or in a cookie, and delegating the user checks to a validation function you supply.
How do I install it and register the scheme?
Install it with npm install hapi-auth-jwt2 and save, register the plugin on your server, then define a strategy with a key or a key lookup function and your validate function, and set it as the default authentication scheme.
What does the validate function receive and return?
It receives the decoded and verified token, the original request and the response toolkit, and returns an object with an isValid flag, optional alternative credentials and an optional response.
Can the signing key be looked up dynamically?
Yes. Instead of a static key or an array of keys you can supply an asynchronous lookup function that receives the decoded but unverified token and returns the key to use, plus optional extra information that is then available to the validation function through the request plugins object.
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/dwyl-hapi-auth-jwt2)Was this useful?