---
title: "JWT and JWK"
description: "Hono provides native JSON Web Token (JWT) and JSON Web Key (JWK) utilities alongside dedicated middleware handlers to authenticate HTTP requests securely across Web Standard runtimes. By leveraging..."
last_updated: "2026-10-12T08:14:12.743647+00:00"
canonical_url: "https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/security-middleware/jwt-and-jwk"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [src/utils/jwt/jwt.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jwt.ts)
- [src/utils/jwt/jws.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jws.ts)
- [src/middleware/jwk/jwk.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/jwk/jwk.ts)
- [src/middleware/secure-headers/secure-headers.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/secure-headers/secure-headers.ts)
- [src/middleware/jwk/keys.test.json](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/jwk/keys.test.json)
- [src/middleware/jwt/jwt.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/jwt/jwt.ts)
- [src/middleware/bearer-auth/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/bearer-auth/index.ts)
- [src/middleware/cache/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cache/index.ts)
- [src/middleware/basic-auth/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/basic-auth/index.ts)
- [src/middleware/csrf/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/csrf/index.ts)
- [src/middleware/etag/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/etag/index.ts)
- [src/middleware/ip-restriction/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts)
- [src/helper/cookie/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/cookie/index.ts)
- [src/utils/jwt/types.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/types.ts)
- [src/request.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/request.ts)
- [src/adapter/lambda-edge/handler.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/lambda-edge/handler.ts)
- [src/middleware/jwk/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/jwk/index.ts)
- [src/utils/crypto.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/crypto.ts)
- [src/middleware/language/language.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/language/language.ts)
- [src/utils/jwt/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/index.ts)
- [src/client/fetch-result-please.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts)
- [src/middleware/jwt/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/jwt/index.ts)
- [package.json](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/package.json)
- [src/context.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/context.ts)
- [src/utils/cookie.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/cookie.ts)
- [src/utils/jwt/jwa.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jwa.ts)
- [src/utils/jwt/utf8.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/utf8.ts)
- [jsr.json](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/jsr.json)
- [src/router/linear-router/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/router/linear-router/index.ts)
- [src/helper/accepts/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/accepts/index.ts)
</details>

## Overview

Hono provides native JSON Web Token (JWT) and JSON Web Key (JWK) utilities alongside dedicated middleware handlers to authenticate HTTP requests securely across Web Standard runtimes. By leveraging `crypto.subtle`, the implementation handles cryptographic signing, signature verification, and dynamic key resolution without external cryptographic dependencies.

Sources: [src/utils/jwt/jwt.ts:1-29](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jwt.ts#L1-L29), [src/utils/jwt/jws.ts:1-12](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jws.ts#L1-L12), [src/middleware/jwt/jwt.ts:1-20](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/jwt/jwt.ts#L1-L20), [src/middleware/jwk/jwk.ts:1-16](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/jwk/jwk.ts#L1-L16)

## Token Signing and Key Resolution

### Signing Workflow and Serialisation

Token generation begins by serialising the payload and header objects into Base64Url-encoded strings without padding. The `sign` function first encodes the `JWTPayload` object using `encodeJwtPart`, which converts the JSON-serialised payload into UTF-8 bytes and transforms it via `encodeBase64Url`, stripping any trailing `=` characters.

```typescript
const encodeJwtPart = (part: unknown): string =>
  encodeBase64Url(utf8Encoder.encode(JSON.stringify(part)).buffer).replace(/=/g, '')
const encodeSignaturePart = (buf: ArrayBufferLike): string => encodeBase64Url(buf).replace(/=/g, '')
```
Sources: [src/utils/jwt/jwt.ts:31-34](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jwt.ts#L31-L34)

Next, the header is constructed. If the supplied `privateKey` is an object containing an `alg` property, the algorithm and optional `kid` (Key ID) are extracted into the header. Otherwise, the provided algorithm parameter (defaulting to `'HS256'`) is used alongside `typ: 'JWT'`. The header and payload are concatenated with a dot delimiter (`${encodedHeader}.${encodedPayload}`) to form the partial token.

```typescript
export const sign = async (
  payload: JWTPayload,
  privateKey: SignatureKey,
  alg: SignatureAlgorithm = 'HS256'
): Promise<string> => {
  const encodedPayload = encodeJwtPart(payload)
  let encodedHeader
  if (typeof privateKey === 'object' && 'alg' in privateKey) {
    alg = privateKey.alg as SignatureAlgorithm
    encodedHeader = encodeJwtPart({ alg, typ: 'JWT', kid: privateKey.kid })
  } else {
    encodedHeader = encodeJwtPart({ alg, typ: 'JWT' })
  }

  const partialToken = `${encodedHeader}.${encodedPayload}`

  const signaturePart = await signing(privateKey, alg, utf8Encoder.encode(partialToken))
  const signature = encodeSignaturePart(signaturePart)

  return `${partialToken}.${signature}`
}
```
Sources: [src/utils/jwt/jwt.ts:56-76](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jwt.ts#L56-L76)

### Call-Chain Execution Walkthrough

When `sign` invokes the signing routine, execution flows through the JWS and Web Crypto resolution layers.

1. `sign` in `src/utils/jwt/jwt.ts` invokes `signing` with the private key, algorithm identifier, and encoded partial token bytes.
2. `signing` in `src/utils/jwt/jws.ts` calls `getKeyAlgorithm(alg)` to map the string algorithm to a Web Crypto `KeyAlgorithm` parameter structure.
3. `getKeyAlgorithm` inspects the algorithm name; if an unsupported identifier is supplied, it falls through or throws `JwtAlgorithmNotImplemented`.
4. `signing` then calls `importPrivateKey(privateKey, algorithm)` to prepare the cryptographic key instance.
5. `crypto.subtle.sign` executes against the imported `CryptoKey` to produce the raw signature buffer, which is subsequently encoded and appended to the token string.

```mermaid
sequenceDiagram
    participant jwt.ts
    participant jws.ts
    participant jwa.ts
    participant types.ts

    jwt.ts->>jws.ts: signing(privateKey, alg, data)
    jws.ts->>jws.ts: getKeyAlgorithm(alg)
    alt algorithm unsupported
        jwa.ts-->>types.ts: JwtAlgorithmNotImplemented
    end
    jws.ts->>jws.ts: importPrivateKey(privateKey, algorithm)
    jws.ts->>crypto.subtle: sign(algorithm, cryptoKey, data)
    crypto.subtle-->>jws.ts: ArrayBuffer signature
    jws.ts-->>jwt.ts: raw signature buffer
```
Sources: [src/utils/jwt/jwt.ts:72-72](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jwt.ts#L72-L72), [src/utils/jwt/jws.ts:29-37](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jws.ts#L29-L37), [src/utils/jwt/jws.ts:122-179](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jws.ts#L122-L179), [src/utils/jwt/types.ts:6-11](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/types.ts#L6-L11)

### Web Crypto Key Resolution

The `importPrivateKey` function handles diverse key representations, including raw strings, PEM-encoded structures, JWK objects, and existing `CryptoKey` instances. It ensures that the resulting key is assigned the `CryptoKeyUsage.Sign` usage flag.

```typescript
async function importPrivateKey(key: SignatureKey, alg: KeyImporterAlgorithm): Promise<CryptoKey> {
  if (!crypto.subtle || !crypto.subtle.importKey) {
    throw new Error('`crypto.subtle.importKey` is undefined. JWT auth middleware requires it.')
  }
  if (isCryptoKey(key)) {
    if (key.type !== 'private' && key.type !== 'secret') {
      throw new Error(
        `unexpected key type: CryptoKey.type is ${key.type}, expected private or secret`
      )
    }
    return key
  }
  const usages = [CryptoKeyUsage.Sign]
  if (typeof key === 'object') {
    return await crypto.subtle.importKey('jwk', key, alg, false, usages)
  }
  if (key.includes('PRIVATE')) {
    return await crypto.subtle.importKey('pkcs8', pemToBinary(key), alg, false, usages)
  }
  return await crypto.subtle.importKey('raw', utf8Encoder.encode(key), alg, false, usages)
}
```
Sources: [src/utils/jwt/jws.ts:54-76](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jws.ts#L54-L76)

> [!WARNING]
> When passing an existing `CryptoKey` to `importPrivateKey`, its `type` property must be either `'private'` or `'secret'`. Passing a public key where a private or secret key is required causes an immediate error.

Sources: [src/utils/jwt/jws.ts:58-64](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jws.ts#L58-L64)

## Token Verification and Claims Validation

### Claims Verification Options

Token validation begins with structural decoding and signature verification, separating cryptographic checks from payload claims assertions. The `decode` function splits the token by periods, verifying that exactly three parts exist before parsing the header and payload components.
Sources: [src/utils/jwt/jwt.ts:264-279](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jwt.ts#L264-L279)

```typescript
export const decode = (token: string): { header: TokenHeader; payload: JWTPayload } => {
  const parts = token.split('.')
  if (parts.length !== 3) {
    throw new JwtTokenInvalid(token)
  }
  try {
    const header = decodeJwtPart(parts[0]) as TokenHeader
    const payload = decodeJwtPart(parts[1]) as JWTPayload
    return {
      header,
      payload,
    }
  } catch {
    throw new JwtTokenInvalid(token)
  }
}
```
Sources: [src/utils/jwt/jwt.ts:264-279](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jwt.ts#L264-L279)

The `VerifyOptions` type configures how standard registered claims are validated against the current system time and expected constraints during token verification.
Sources: [src/utils/jwt/jwt.ts:78-94](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jwt.ts#L78-L94)

| Option | Type | Default | Purpose |
| :--- | :--- | :--- | :--- |
| `iss` | `string \| RegExp` | — | Expected issuer used for verifying the `iss` claim |
| `nbf` | `boolean` | `true` | Verify that the current timestamp is after or equal to the `nbf` claim |
| `exp` | `boolean` | `true` | Verify that the current timestamp is before the `exp` claim |
| `iat` | `boolean` | `true` | Verify that the `iat` claim is a valid number not in the future |
| `aud` | `string \| string[] \| RegExp` | — | Expected audience used for verifying the `aud` claim |
| `alg` | `SignatureAlgorithm` | — | Required signature algorithm for verification |

Sources: [src/utils/jwt/jwt.ts:78-94](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jwt.ts#L78-L94)

> [!NOTE]
> If the `alg` option is omitted during verification, the verifier immediately throws a `JwtAlgorithmRequired` error to prevent algorithm substitution vulnerabilities.
Sources: [src/utils/jwt/jwt.ts:99-101](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jwt.ts#L99-L101)

## JSON Web Key Set Middleware

### JWKS Retrieval and Key Matching

The `verifyWithJwks` function retrieves and validates JSON Web Key Sets (JWKS) to verify incoming tokens without pre-configured static keys. It first decodes the token header and checks for a valid header structure and a mandatory `kid` (Key ID) property. If the header lacks a `kid`, `JwtHeaderRequiresKid` is thrown.
Sources: [src/utils/jwt/jwt.ts:209-216](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jwt.ts#L209-L216)

```typescript
export const verifyWithJwks = async (
  token: string,
  options: {
    keys?: HonoJsonWebKey[]
    jwks_uri?: string
    verification?: VerifyOptions
    allowedAlgorithms: readonly AsymmetricAlgorithm[]
  },
  init?: RequestInit
): Promise<JWTPayload> => {
  const verifyOpts = options.verification || {}

  const header = decodeHeader(token)

  if (!isTokenHeader(header)) {
    throw new JwtHeaderInvalid(header)
  }
  if (!header.kid) {
    throw new JwtHeaderRequiresKid(header)
  }
```
Sources: [src/utils/jwt/jwt.ts:197-216](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jwt.ts#L197-L216)

> [!WARNING]
> Symmetric algorithms such as `HS256`, `HS384`, and `HS512` are explicitly prohibited during JWKS verification. Any token header using a symmetric algorithm causes `verifyWithJwks` to throw a `JwtSymmetricAlgorithmNotAllowed` error to prevent algorithm confusion attacks.
Sources: [src/utils/jwt/jwt.ts:190-221](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jwt.ts#L190-L221)

```typescript
// Reject symmetric algorithms (HS256, HS384, HS512) to prevent algorithm confusion attacks
if (symmetricAlgorithms.includes(header.alg as SymmetricAlgorithm)) {
  throw new JwtSymmetricAlgorithmNotAllowed(header.alg)
}

// Validate against allowed algorithms
if (!options.allowedAlgorithms.includes(header.alg as AsymmetricAlgorithm)) {
  throw new JwtAlgorithmNotAllowed(header.alg, options.allowedAlgorithms)
}
```
Sources: [src/utils/jwt/jwt.ts:218-226](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jwt.ts#L218-L226)

When an `options.jwks_uri` is supplied, `verifyWithJwks` fetches the URI using the optional `RequestInit` init argument, parses the JSON response, and ensures that a `keys` array exists. These fetched keys are merged with any statically provided `options.keys`. The verifier then finds the specific key where `key.kid === header.kid`.
Sources: [src/utils/jwt/jwt.ts:228-251](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jwt.ts#L228-L251)

```typescript
  let verifyKeys = options.keys ? [...options.keys] : undefined

  if (options.jwks_uri) {
    const response = await fetch(options.jwks_uri, init)
    if (!response.ok) {
      throw new Error(`failed to fetch JWKS from ${options.jwks_uri}`)
    }
    const data = (await response.json()) as { keys?: JsonWebKey[] }
    if (!data.keys) {
      throw new Error('invalid JWKS response. "keys" field is missing')
    }
    if (!Array.isArray(data.keys)) {
      throw new Error('invalid JWKS response. "keys" field is not an array')
    }
    verifyKeys ??= []
    verifyKeys.push(...(data.keys as HonoJsonWebKey[]))
  } else if (!verifyKeys) {
    throw new Error('verifyWithJwks requires options for either "keys" or "jwks_uri" or both')
  }

  const matchingKey = verifyKeys.find((key) => key.kid === header.kid)
  if (!matchingKey) {
    throw new JwtTokenInvalid(token)
  }

  // Verify that JWK's alg matches JWT header's alg when JWK has alg field
  if (matchingKey.alg && matchingKey.alg !== header.alg) {
    throw new JwtAlgorithmMismatch(matchingKey.alg, header.alg)
  }

  return await verify(token, matchingKey, {
    alg: header.alg,
    ...verifyOpts,
  })
}
```
Sources: [src/utils/jwt/jwt.ts:228-262](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jwt.ts#L228-L262)

### Verification Execution and Call Chain

The execution flow for JWKS-based verification proceeds through structural header inspection and key resolution.

1. `verifyWithJwks` — Entry point for verifying tokens against local keys or a remote JWKS endpoint.
Sources: [src/utils/jwt/jwt.ts:197-210](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jwt.ts#L197-L210)
2. `decodeHeader` — Splits the token by periods and decodes the first segment into a `TokenHeader` object.
Sources: [src/utils/jwt/jwt.ts:281-290](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jwt.ts#L281-L290)
3. `JwtTokenInvalid` — Thrown if the token does not contain exactly three parts or if part decoding fails.
Sources: [src/utils/jwt/jwt.ts:265-268](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jwt.ts#L265-L268), [src/utils/jwt/jwt.ts:283-289](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jwt.ts#L283-L289)

```mermaid
sequenceDiagram
    participant verifyWithJwks as src/utils/jwt/jwt.ts
    participant decodeHeader as src/utils/jwt/jwt.ts
    participant JwtTokenInvalid as src/utils/jwt/types.ts
    verifyWithJwks->>decodeHeader: decodeHeader(token)
    decodeHeader->>JwtTokenInvalid: throw JwtTokenInvalid(token) if parts length !== 3
```
Sources: [src/utils/jwt/jwt.ts:209-210](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jwt.ts#L209-L210), [src/utils/jwt/jwt.ts:281-290](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jwt.ts#L281-L290)

> [!NOTE]
> If a matching JWK defines an explicit `alg` property, it must strictly match the `alg` declared in the JWT header. A mismatch causes a `JwtAlgorithmMismatch` exception.
Sources: [src/utils/jwt/jwt.ts:253-256](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jwt.ts#L253-L256)

## JWT Authentication Middleware Flow

### Credential Extraction and Token Resolution

The `jwt` middleware intercepts incoming HTTP requests to locate and validate JSON Web Tokens from configured request headers or cookies. It first checks the request headers using `options.headerName` (defaulting to `'Authorization'`). When credentials are found in the header, the middleware splits the string by whitespace and expects exactly two parts where the first segment case-insensitively equals `'bearer'`. If the credential structure is invalid, it throws an `HTTPException` with status code `401`.
Sources: [src/middleware/jwt/jwt.ts:76-95](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/jwt/jwt.ts#L76-L95)

```typescript
return async function jwt(ctx, next) {
  const headerName = options.headerName || 'Authorization'

  const credentials = ctx.req.raw.headers.get(headerName)
  let token
  if (credentials) {
    const parts = credentials.split(/\s+/)
    if (parts.length !== 2 || parts[0].toLowerCase() !== 'bearer') {
      const errDescription = 'invalid credentials structure'
      throw new HTTPException(401, {
        message: errDescription,
        res: unauthorizedResponse({
          ctx,
          error: 'invalid_request',
          errDescription,
        }),
      })
    } else {
      token = parts[1]
    }
```
Sources: [src/middleware/jwt/jwt.ts:76-95](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/jwt/jwt.ts#L76-L95)

If no header credentials exist, the middleware evaluates `options.cookie`. The `cookie` option can be provided as a plain string key or an object containing `key`, an optional `secret` for signed cookies, and optional `prefixOptions`. Depending on these options, the token is extracted using `getCookie` or `getSignedCookie`.
Sources: [src/middleware/jwt/jwt.ts:96-117](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/jwt/jwt.ts#L96-L117)

```typescript
} else if (options.cookie) {
  if (typeof options.cookie == 'string') {
    token = getCookie(ctx, options.cookie)
  } else if (options.cookie.secret) {
    if (options.cookie.prefixOptions) {
      token = await getSignedCookie(
        ctx,
        options.cookie.secret,
        options.cookie.key,
        options.cookie.prefixOptions
      )
    } else {
      token = await getSignedCookie(ctx, options.cookie.secret, options.cookie.key)
    }
  } else {
    if (options.cookie.prefixOptions) {
      token = getCookie(ctx, options.cookie.key, options.cookie.prefixOptions)
    } else {
      token = getCookie(ctx, options.cookie.key)
    }
  }
}
```
Sources: [src/middleware/jwt/jwt.ts:96-117](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/jwt/jwt.ts#L96-L117)

> [!WARNING]
> If neither header credentials nor a valid cookie yields a token string, the middleware immediately rejects the request with a `401` HTTP exception and an `invalid_request` error description.
Sources: [src/middleware/jwt/jwt.ts:119-129](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/jwt/jwt.ts#L119-L129)

```typescript
if (!token) {
  const errDescription = 'no authorization included in request'
  throw new HTTPException(401, {
    message: errDescription,
    res: unauthorizedResponse({
      ctx,
      error: 'invalid_request',
      errDescription,
    }),
  })
}
```
Sources: [src/middleware/jwt/jwt.ts:119-129](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/jwt/jwt.ts#L119-L129)

### Verification, Context Injection, and 401 Responses

Once a token is extracted, the middleware invokes `Jwt.verify` with the configured `secret`, `alg`, and verification options. If verification fails or throws an error, a `401` exception is raised with an `invalid_token` error and a `WWW-Authenticate` challenge header. Upon successful verification, the decoded payload is stored in the [execution context](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/core-engine/execution-context) under the key `'jwtPayload'`, and control proceeds to the next handler via `await next()`.
Sources: [src/middleware/jwt/jwt.ts:131-157](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/jwt/jwt.ts#L131-L157)

```typescript
  let payload
  let cause
  try {
    payload = await Jwt.verify(token, options.secret, {
      alg: options.alg,
      ...verifyOpts,
    })
  } catch (e) {
    cause = e
  }
  if (!payload) {
    throw new HTTPException(401, {
      message: 'Unauthorized',
      res: unauthorizedResponse({
        ctx,
        error: 'invalid_token',
        statusText: 'Unauthorized',
        errDescription: 'token verification failure',
      }),
      cause,
    })
  }

  ctx.set('jwtPayload', payload)

  await next()
}
```
Sources: [src/middleware/jwt/jwt.ts:131-157](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/jwt/jwt.ts#L131-L157)

Unauthorized responses are constructed via `unauthorizedResponse`, which formats a `401` status response carrying a `WWW-Authenticate` header containing the request URL, error type, and error description.
Sources: [src/middleware/jwt/jwt.ts:160-173](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/jwt/jwt.ts#L160-L173)

```typescript
function unauthorizedResponse(opts: {
  ctx: Context
  error: string
  errDescription: string
  statusText?: string
}) {
  return new Response('Unauthorized', {
    status: 401,
    statusText: opts.statusText,
    headers: {
      'WWW-Authenticate': `Bearer realm="${opts.ctx.req.url}",error="${opts.error}",error_description="${opts.errDescription}"`,
    },
  })
}
```
Sources: [src/middleware/jwt/jwt.ts:160-173](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/jwt/jwt.ts#L160-L173)

## Supported Cryptographic Algorithms and Errors

### Supported JWA Signature Algorithms and Web Crypto Mappings

JSON Web Algorithms (JWA) are mapped to underlying Web Crypto API operations via `getKeyAlgorithm` in the JWS utility module. Supported algorithms encompass HMAC-SHA variants (`HS256`, `HS384`, `HS512`), RSA signatures using PKCS#1 v1.5 (`RS256`, `RS384`, `RS512`), RSA Probabilistic Signature Scheme (`PS256`, `PS384`, `PS512`), and Elliptic Curve Digital Signature Algorithm variants (`ES256`, `ES384`, `ES512`) alongside `EdDSA`.

| Algorithm | JWA Category | Web Crypto Algorithm Name | Hash Function | Additional Parameters |
| :--- | :--- | :--- | :--- | :--- |
| `HS256` | Symmetric | `HMAC` | `SHA-256` | — |
| `HS384` | Symmetric | `HMAC` | `SHA-384` | — |
| `HS512` | Symmetric | `HMAC` | `SHA-512` | — |
| `RS256` | Asymmetric | `RSASSA-PKCS1-v1_5` | `SHA-256` | — |
| `RS384` | Asymmetric | `RSASSA-PKCS1-v1_5` | `SHA-384` | — |
| `RS512` | Asymmetric | `RSASSA-PKCS1-v1_5` | `SHA-512` | — |
| `PS256` | Asymmetric | `RSA-PSS` | `SHA-256` | `saltLength: 32` |
| `PS384` | Asymmetric | `RSA-PSS` | `SHA-384` | `saltLength: 48` |
| `PS512` | Asymmetric | `RSA-PSS` | `SHA-512` | `saltLength: 64` |
| `ES256` | Asymmetric | `ECDSA` | `SHA-256` | `namedCurve: 'P-256'` |
| `ES384` | Asymmetric | `ECDSA` | `SHA-384` | `namedCurve: 'P-384'` |
| `ES512` | Asymmetric | `ECDSA` | `SHA-512` | `namedCurve: 'P-521'` |
| `EdDSA` | Asymmetric | `Ed25519` | — | — |

Sources: [src/utils/jwt/jwa.ts:7-21](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jwa.ts#L7-L21), [src/utils/jwt/jws.ts:122-211](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jws.ts#L122-L211)

```typescript
function getKeyAlgorithm(name: SignatureAlgorithm): KeyAlgorithm {
  switch (name) {
    case 'HS256':
      return {
        name: 'HMAC',
        hash: {
          name: 'SHA-256',
        },
      } satisfies HmacImportParams
    case 'HS384':
      return {
        name: 'HMAC',
        hash: {
          name: 'SHA-384',
        },
      } satisfies HmacImportParams
    case 'HS512':
      return {
        name: 'HMAC',
        hash: {
          name: 'SHA-512',
        },
      } satisfies HmacImportParams
    case 'RS256':
      return {
        name: 'RSASSA-PKCS1-v1_5',
        hash: {
          name: 'SHA-256',
        },
      } satisfies RsaHashedImportParams
    case 'RS384':
      return {
        name: 'RSASSA-PKCS1-v1_5',
        hash: {
          name: 'SHA-384',
        },
      } satisfies RsaHashedImportParams
    case 'RS512':
      return {
        name: 'RSASSA-PKCS1-v1_5',
        hash: {
          name: 'SHA-512',
        },
      } satisfies RsaHashedImportParams
    case 'PS256':
      return {
        name: 'RSA-PSS',
        hash: {
          name: 'SHA-256',
        },
        saltLength: 32, // 256 >> 3
      } satisfies RsaPssParams & RsaHashedImportParams
    case 'PS384':
      return {
        name: 'RSA-PSS',
        hash: {
          name: 'SHA-384',
        },
        saltLength: 48, // 384 >> 3
      } satisfies RsaPssParams & RsaHashedImportParams
    case 'PS512':
      return {
        name: 'RSA-PSS',
        hash: {
          name: 'SHA-512',
        },
        saltLength: 64, // 512 >> 3,
      } satisfies RsaPssParams & RsaHashedImportParams
    case 'ES256':
      return {
        name: 'ECDSA',
        hash: {
          name: 'SHA-256',
        },
        namedCurve: 'P-256',
      } satisfies EcdsaParams & EcKeyImportParams
    case 'ES384':
      return {
        name: 'ECDSA',
        hash: {
```
Sources: [src/utils/jwt/jws.ts:122-211](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jws.ts#L122-L211)

> [!CAUTION]
> For `PS256`, `PS384`, and `PS512`, the salt length is strictly derived from the hash bit length divided by eight (`256 >> 3` yields `32`, `384 >> 3` yields `48`, and `512 >> 3` yields `64`). Specifying a custom or non-standard salt length outside these Web Crypto mappings will cause signature verification or generation to fail.
Sources: [src/utils/jwt/jws.ts:166-194](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/jws.ts#L166-L194)

### Custom Error Types

JWT parsing, validation, and cryptographic routines throw specialized error classes defined in `src/utils/jwt/types.ts`. Each error sets a descriptive message and assigns its `name` property to match the class identifier.

| Error Class | Constructor Arguments | Error Message Pattern |
| :--- | :--- | :--- |
| `JwtAlgorithmNotImplemented` | `alg: string` | `${alg} is not an implemented algorithm` |
| `JwtAlgorithmRequired` | none | `JWT verification requires "alg" option to be specified` |
| `JwtAlgorithmMismatch` | `expected: string, actual: string` | `JWT algorithm mismatch: expected "${expected}", got "${actual}"` |
| `JwtTokenInvalid` | `token: string` | `invalid JWT token: ${token}` |
| `JwtTokenNotBefore` | `token: string` | `token (${token}) is being used before it's valid` |
| `JwtTokenExpired` | `token: string` | `token (${token}) expired` |
| `JwtTokenIssuedAt` | `currentTimestamp: number, iat: number` | `Invalid "iat" claim, must be a valid number lower than "${currentTimestamp}" (iat: "${iat}")` |
| `JwtTokenIssuer` | `expected: string \| RegExp, iss: string \| null` | `expected issuer "${expected}", got ${iss ? `"${iss}"` : 'none'} ` |
| `JwtHeaderInvalid` | `header: object` | `jwt header is invalid: ${JSON.stringify(header)}` |
| `JwtHeaderRequiresKid` | `header: object` | `required "kid" in jwt header: ${JSON.stringify(header)}` |
| `JwtSymmetricAlgorithmNotAllowed` | `alg: string` | `symmetric algorithm "${alg}" is not allowed for JWK verification` |
| `JwtAlgorithmNotAllowed` | `alg: string, allowedAlgorithms: readonly string[]` | `algorithm "${alg}" is not in the allowed list: [...]` |
| `JwtTokenSignatureMismatched` | `token: string` | `token(${token}) signature mismatched` |
| `JwtPayloadRequiresAud` | `payload: object` | `required "aud" in jwt payload: ${JSON.stringify(payload)}` |
| `JwtTokenAudience` | `expected: string \| string[] \| RegExp, aud: string \| string[]` | `expected audience "${...}", got "${aud}"` |

Sources: [src/utils/jwt/types.ts:6-115](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/types.ts#L6-L115)

```typescript
export class JwtAlgorithmNotImplemented extends Error {
  constructor(alg: string) {
    super(`${alg} is not an implemented algorithm`)
    this.name = 'JwtAlgorithmNotImplemented'
  }
}

export class JwtAlgorithmRequired extends Error {
  constructor() {
    super('JWT verification requires "alg" option to be specified')
    this.name = 'JwtAlgorithmRequired'
  }
}

export class JwtAlgorithmMismatch extends Error {
  constructor(expected: string, actual: string) {
    super(`JWT algorithm mismatch: expected "${expected}", got "${actual}"`)
    this.name = 'JwtAlgorithmMismatch'
  }
}

export class JwtTokenInvalid extends Error {
  constructor(token: string) {
    super(`invalid JWT token: ${token}`)
    this.name = 'JwtTokenInvalid'
  }
}

export class JwtTokenNotBefore extends Error {
  constructor(token: string) {
    super(`token (${token}) is being used before it's valid`)
    this.name = 'JwtTokenNotBefore'
  }
}

export class JwtTokenExpired extends Error {
  constructor(token: string) {
    super(`token (${token}) expired`)
    this.name = 'JwtTokenExpired'
  }
}

export class JwtTokenIssuedAt extends Error {
  constructor(currentTimestamp: number, iat: number) {
    super(
      `Invalid "iat" claim, must be a valid number lower than "${currentTimestamp}" (iat: "${iat}")`
    )
    this.name = 'JwtTokenIssuedAt'
  }
}

export class JwtTokenIssuer extends Error {
  constructor(expected: string | RegExp, iss: string | null) {
    super(`expected issuer "${expected}", got ${iss ? `"${iss}"` : 'none'} `)
    this.name = 'JwtTokenIssuer'
  }
}

export class JwtHeaderInvalid extends Error {
  constructor(header: object) {
    super(`jwt header is invalid: ${JSON.stringify(header)}`)
    this.name = 'JwtHeaderInvalid'
  }
}

export class JwtHeaderRequiresKid extends Error {
  constructor(header: object) {
    super(`required "kid" in jwt header: ${JSON.stringify(header)}`)
    this.name = 'JwtHeaderRequiresKid'
  }
}

export class JwtSymmetricAlgorithmNotAllowed extends Error {
  constructor(alg: string) {
    super(`symmetric algorithm "${alg}" is not allowed for JWK verification`)
    this.name = 'JwtSymmetricAlgorithmNotAllowed'
  }
}

export class JwtAlgorithmNotAllowed extends Error {
```
Sources: [src/utils/jwt/types.ts:6-115](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/jwt/types.ts#L6-L115)

## Related

- [Basic and Bearer Auth](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/security-middleware/basic-and-bearer-auth)
- [Secure Headers and CORS](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/security-middleware/secure-headers-and-cors)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/llms.txt) for all pages in this wiki.
