Skip to content

@hoajs/jwt ​

JSON Web Token (JWT) middleware for Hoa.

Supported scenarios:

  • HS* verification with a shared secret (e.g., HS256)
  • RS* verification and signing using local PEM/KeyLike (e.g., RS256)
  • Asymmetric verification via remote JWKS (typically RS256)
  • Dynamic secret resolution (function-based, returning key material based on the token)

Quick Start ​

js
import { Hoa } from 'hoa'
import { jwt } from '@hoajs/jwt'

const app = new Hoa()
app.use(jwt({ secret: 'shhhh', algorithms: ['HS256'] }))

app.use(async (ctx) => {
  // After verification, the payload is stored on ctx.state.user (configurable via `key`)
  ctx.res.body = `Hello, ${ctx.state.user.name}!`
})

export default app

Options ​

  • secret: Secret or key material for verification (string/Uint8Array/CryptoKey/KeyLike), or a function (token) => key; can be omitted when using jwksUri only
  • algorithms: Allowed algorithms, default ['HS256']
  • getToken: Custom method to extract the token from the request (default reads from Authorization: Bearer)
  • cookie: When Authorization is empty, read the token from this cookie name
  • key: The key on ctx.state to store the verified payload, default 'user'
  • credentialsRequired: When true, missing token results in 401; when false, calls next() directly. default true
  • passthrough: When true, do not throw on verification failure; just next(). default false
  • isRevoked: Optional revocation check; returning true treats the token as revoked
  • issuer/audience/subject/clockTolerance: Corresponding claim checks and clock skew tolerance
  • jwksUri: Remote JWKS endpoint (typically used with RS256)

Examples ​

Remote JWKS verification ​

js
app.use(jwt({ jwksUri: 'https://example.com/.well-known/jwks.json', algorithms: ['RS256'] }))

Note: When jwksUri is provided, verification uses the remote key set (JWKS). If neither jwksUri nor secret is configured, verification will throw "Verification secret is not configured".

Custom token extraction ​

js
app.use(jwt({
  secret: 'shhhh',
  algorithms: ['HS256'],
  getToken: (ctx) => ctx.req.get('X-Auth') || null
}))
js
app.use(jwt({ secret: 'shhhh', algorithms: ['HS256'], cookie: 'auth' }))

Customize ctx.state key ​

js
app.use(jwt({ secret: 'shhhh', algorithms: ['HS256'], key: 'auth' }))
app.use(async (ctx) => {
  ctx.res.body = `Welcome, ${ctx.state.auth.name}!`
})

Passthrough and revocation check ​

js
app.use(jwt({ secret: 'shhhh', algorithms: ['HS256'], passthrough: true }))
app.use(async (ctx) => {
  // Even if the token is invalid, control continues here (passthrough=true)
  ctx.res.body = 'OK'
})

app.use(jwt({
  secret: 'shhhh',
  algorithms: ['HS256'],
  isRevoked: async (ctx, payload) => {
    // token revocation logic here
  }
}))

Utilities ​

signJWT ​

signJWT(payload, secret, options): Generate a signed token; for RS* pass a private key PEM/KeyLike, for HS* pass a shared secret string

  • payload: Object/JWTPayload, the claims to include in the token (custom fields allowed). Issued-at (iat) is set automatically.
  • secret: string | Uint8Array | CryptoKey | KeyLike. For HS* pass a shared secret string; for RS* pass a private key (PEM or KeyLike).
  • options:
    • algorithm: string. Default 'HS256'. Example: 'HS256', 'RS256'.
    • issuer: string. Sets the iss claim.
    • audience: string. Sets the aud claim.
    • subject: string. Sets the sub claim.
    • expiresIn: string | number. Expiration, e.g. '1h', '30m', or seconds.
    • header: JWS header parameters. Merged into protected header (e.g., { kid: 'key-id' }).
  • returns: Promise<string> – the compact JWT string.

HS256 signing:

js
import { signJWT } from '@hoajs/jwt'

const secret = 'shhhh'
const token = await signJWT(
  { name: 'Alice' },
  secret,
  { algorithm: 'HS256', expiresIn: '1h', header: { kid: 'hs1' } }
)

RS256 signing with PEM:

js
import { signJWT } from '@hoajs/jwt'

const privatePem = `-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----`
const token = await signJWT(
  { uid: 1 },
  privatePem,
  { algorithm: 'RS256', expiresIn: '1h' }
)

verifyJWT ​

verifyJWT(token, options): Verify a token; supports secret/jwksUri/function-based secret

  • token: string. The JWT compact string to verify.
  • options:
    • secret: string | Uint8Array | CryptoKey | KeyLike | (token) => Promise<secret>. Verification key/secret. For RS* typically a public key (PEM/KeyLike). When using jwksUri, secret can be omitted.
    • algorithms: string[]. Allowed algorithms. Default ['HS256']; set to ['RS256'] for RS256/JWKS.
    • issuer: string | string[]. Expected issuer(s) to validate the iss claim.
    • audience: string | string[]. Expected audience(s) to validate the aud claim.
    • subject: string. Expected subject to validate the sub claim.
    • clockTolerance: string | number. Allowed clock skew (e.g., '5s' or 5).
    • jwksUri: string. Remote JWKS endpoint for asymmetric verification (uses a remote key set).
  • returns: Promise<{ payload: JWTPayload; protectedHeader: JWSHeaderParameters }>

Verify HS256 token with a shared secret:

js
import { verifyJWT } from '@hoajs/jwt'

const secret = 'shhhh'
const { payload, protectedHeader } = await verifyJWT(token, {
  secret,
  algorithms: ['HS256'],
  issuer: 'example-issuer',
  audience: 'example-audience',
  clockTolerance: '5s'
})

Verify RS256 token with public PEM:

js
import { verifyJWT } from '@hoajs/jwt'

const publicPem = `-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----`
const { payload } = await verifyJWT(token, {
  secret: publicPem,
  algorithms: ['RS256']
})

Verify via remote JWKS:

js
import { verifyJWT } from '@hoajs/jwt'

const { payload } = await verifyJWT(tokenFromClient, {
  jwksUri: 'https://example.com/.well-known/jwks.json',
  algorithms: ['RS256']
})

Verify with function-based secret (dynamic):

js
import { verifyJWT } from '@hoajs/jwt'

const { payload } = await verifyJWT(token, {
  secret: async (t) => process.env.JWT_SECRET,
  algorithms: ['HS256']
})