koa-jwt

JWT authentication middleware that validates `Authorization: Bearer <token>` headers and populates `ctx.state.user` with the decoded payload.

Since Koa 2 Spec ↗

Syntax

import jwt from 'koa-jwt';
app.use(jwt({ secret }));

Parameters

NameTypeRequiredDescription
options object Yes Options: `secret` (string or Buffer, **required**), `key` (property name on `ctx.state`, default `"user"`), `passthrough` (allow unauthenticated requests), `algorithms` (allowed JWT algorithms, default `['HS256']`), `getToken` (custom token extractor function).

Returns

function — Koa middleware that verifies the JWT and sets `ctx.state.user`.

Throws

  • HttpError — The token is missing (401) or invalid/expired (401).

Examples

import Koa from 'koa';
import jwt from 'koa-jwt';
import Router from '@koa/router';
import { sign } from 'jsonwebtoken';

const SECRET = process.env.JWT_SECRET ?? 'dev-secret';
const app = new Koa();
const router = new Router();

// Public route — no JWT required
router.post('/auth/token', async (ctx) => {
  const token = sign({ sub: 'user:42', role: 'admin' }, SECRET, {
    expiresIn: '1h',
  });
  ctx.body = { token };
});

// Protect all routes below with JWT
app.use(jwt({ secret: SECRET }));

router.get('/me', async (ctx) => {
  ctx.body = ctx.state.user; // decoded payload
});

app.use(router.routes());
app.listen(3000);
Output
POST /auth/token → {"token":"eyJ..."}
GET  /me (no token) → 401 Unauthorized
GET  /me (valid token) → {"sub":"user:42","role":"admin","iat":...}

Notes

Mount the `jwt()` middleware after any public routes. Use `passthrough: true` to allow unauthenticated access and check `ctx.state.user` manually. For RS256 or ES256, pass the public key (PEM) as `secret`. Pair with an allow-list middleware to support refresh-token invalidation.

See also