Koa JWT Authentication Middleware
koa-jwt2 is Koa middleware designed for authenticating HTTP requests using JSON Web Tokens (JWT). It validates incoming JWTs and populates `ctx.state.user` (or a configurable property) with the decoded payload, making it available for subsequent middleware to handle authorization and access control. Key features include support for `audience`, `issuer`, and `expiration` validation, handling of base64 URL-encoded secrets, and verification with public/private key pairs. It integrates `koa-unless` for specifying unprotected paths and offers advanced options like custom token extraction via `getToken` and multi-tenancy support through an asynchronous secret function. The current stable version is 1.0.3. However, the package's GitHub repository has been archived, indicating it is no longer actively maintained, and thus its release cadence is effectively ceased. This makes it distinct from more actively developed alternatives, though its multi-tenancy secret resolution feature remains notable.
Common errors
-
TypeError: jwt is not a function
cause Attempting to use an ES module import statement (`import jwt from 'koa-jwt2'`) in a CommonJS project, or incorrectly destructuring the `require` result.fixEnsure you are using `const jwt = require('koa-jwt2');` as this package is primarily CommonJS. If using `type: 'module'` in `package.json`, you might need to use `import jwt from 'koa-jwt2'` if a default export is provided, but this specific package's primary usage is CJS. -
Error: Unauthorized
cause The JWT provided in the request was either missing, invalid (e.g., expired, wrong signature, incorrect audience/issuer), or the `credentialsRequired` option was set to `true` and no token was present.fixVerify the token's validity (signature, expiry, claims like audience/issuer). Check that the token is sent in the `Authorization: Bearer <token>` header, or configure `getToken` if it's in a query parameter or cookie. Ensure the `secret` configured matches the secret used to sign the token. -
Error: missing_secret
cause In a multi-tenancy setup, the asynchronous `secret` function failed to resolve a secret for the given payload, often because the identified issuer or tenant was not found.fixReview the logic within your `secret` async function. Ensure that `data.getTenantByIdentifier` or equivalent logic correctly retrieves a tenant and its secret, and that any `reject(new Error('missing_secret'))` paths are intended and handled. Debug the `payload.iss` value to confirm it matches expected issuer identifiers. -
JsonWebTokenError: invalid signature
cause The secret or public key used by `koa-jwt2` to verify the token does not match the secret or private key used to sign the token. This often happens due to a mismatch between environments or incorrect key loading.fixDouble-check the `secret` configuration. Ensure the string secret is identical, or that the `Buffer` for a base64 secret or public key is loaded correctly and contains the exact bytes. Verify that no encoding issues are corrupting the key or secret.
Warnings
- breaking The `koa-jwt2` package's GitHub repository has been archived, indicating it is no longer actively maintained. This means there will be no further bug fixes, security patches, or new features. Using this package in production carries increased risk, especially regarding potential unpatched security vulnerabilities.
- gotcha When using a base64 URL-encoded secret or a public key from a file, the `secret` option must be a Node.js `Buffer` object, not a string. Passing a string for these cases will lead to incorrect token verification.
- gotcha By default, `koa-jwt2` attaches the decoded token payload to `ctx.state.user`. If you have other middleware or processes that rely on `ctx.state.user` for different purposes, this could lead to conflicts or unexpected overwrites. This can be particularly problematic if `ctx.state.user` is also used for session management.
- gotcha The `credentialsRequired: false` option allows requests to proceed without a valid JWT. While useful for optional authentication, it can be a security footgun if not correctly understood. Routes using this option might mistakenly expose sensitive data if subsequent authorization logic is not robust.
- gotcha When implementing multi-tenancy with an asynchronous `secret` function, ensure proper error handling within the function. If the `secret` function rejects or throws an error (e.g., 'missing_secret'), it will propagate up and typically result in a 500 Internal Server Error unless a global error handler is configured for Koa.
Install
-
npm install koa-jwt2 -
yarn add koa-jwt2 -
pnpm add koa-jwt2
Imports
- jwt
import jwt from 'koa-jwt2';
const jwt = require('koa-jwt2'); - jwt middleware function
app.use(jwt({ secret: 'your-secret' })); - jwt().unless
app.use(unless({ path: ['/public'] })(jwt({ secret: 'your-secret' })));app.use(jwt({ secret: 'your-secret' }).unless({ path: ['/public'] }));
Quickstart
const Koa = require('koa');
const Router = require('@koa/router');
const jwt = require('koa-jwt2');
const app = new Koa();
const router = new Router();
const SECRET = process.env.JWT_SECRET || 'a-very-strong-secret-for-jwt-signing';
// Middleware to generate a simple JWT for testing
router.get('/token', async (ctx) => {
const jsonwebtoken = require('jsonwebtoken');
const token = jsonwebtoken.sign({ id: 1, name: 'testuser', admin: false }, SECRET, { expiresIn: '1h' });
ctx.body = { token };
});
// Protected route
router.get('/protected', jwt({ secret: SECRET }).unless({ path: ['/token'] }), async (ctx) => {
if (!ctx.state.user) {
ctx.status = 401;
ctx.body = { message: 'Authentication required' };
return;
}
ctx.body = { message: `Hello, ${ctx.state.user.name}! You accessed a protected route.`, user: ctx.state.user };
});
app.use(router.routes()).use(router.allowedMethods());
const port = 3000;
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
console.log('GET /token to get a JWT.');
console.log('GET /protected with Authorization: Bearer <token> header.');
});