npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@chrislyons-dev/flarelette-hono

v1.1.0

Published

Type-safe JWT authentication middleware for Hono on Cloudflare Workers

Downloads

139

Readme

Flarelette-Hono

Framework adapter for Cloudflare Workers built on Hono + Flarelette JWT. Provides clean, declarative JWT authentication and policy enforcement for micro-APIs running on Cloudflare.


Overview

flarelette-hono is the Hono adapter for the @chrislyons-dev/flarelette-jwt toolkit. It adds route-level middleware, context helpers, and environment injection for a fully self-contained API stack.

| Layer | Responsibility | | ---------------------------------- | ----------------------------------------------------------------- | | @chrislyons-dev/flarelette-jwt | Low-level JWT signing, verification, key handling (HS512 + EdDSA) | | flarelette-hono | Middleware, guards, request/response helpers for Hono | | Your Worker | Application logic, routes, business rules |


Design Philosophy

Flarelette-Hono is an auth middleware for Hono, not a full framework.

Unlike the Python flarelette package (which is a complete micro-framework similar to Flask), flarelette-hono is intentionally minimal:

  • Python flarelette: Full framework (routing, middleware, auth, validation, logging, service factory)
  • TypeScript flarelette-hono: Auth middleware for existing Hono framework (JWT auth + optional logging helper)

Why this approach?

Hono is already an excellent edge framework — we don't rewrite it. Instead, flarelette-hono adds what's missing for feature compatibility with Python flarelette:

JWT authentication via authGuard() middleware ✅ Policy-based authorization via policy() builder ✅ Structured logging via optional createLogger() helper (ADR-0013 compliance) ✅ Type-safe context via HonoEnv extension

Everything else (routing, error handling, request/response, middleware chaining) is Hono's responsibility.


Features

  • Framework-native: integrates seamlessly with Hono on Cloudflare Workers
  • JWT middleware: declarative authGuard(policy) for route protection
  • Explicit configuration: authGuardWithConfig() for testing and multi-tenant scenarios
  • Role/permission policies: simple fluent builder (policy().rolesAny().needAll())
  • Env injection: automatically wires Cloudflare bindings (env) into jwt-kit
  • Framework-agnostic core: built directly atop @chrislyons-dev/flarelette-jwt
  • Dual configuration modes: Environment-driven or explicit config objects
  • Type-safe: 100% TypeScript with strict typing — no any types

Quick Start

1. Install

# Core dependencies
npm install hono @chrislyons-dev/flarelette-jwt @chrislyons-dev/flarelette-hono

# Strongly recommended: Input validation
npm install zod @hono/zod-validator

# Or with pnpm
pnpm add hono @chrislyons-dev/flarelette-jwt @chrislyons-dev/flarelette-hono
pnpm add zod @hono/zod-validator

2. Configure Environment

Set required environment variables in wrangler.toml:

[vars]
JWT_ISS = "https://gateway.internal"
JWT_AUD = "your-service-name"
JWT_SECRET_NAME = "INTERNAL_JWT_SECRET"

Generate and store secret:

# IMPORTANT: v1.13+ requires 64-byte minimum for HS512
npx flarelette-jwt-secret --len=64 | wrangler secret put INTERNAL_JWT_SECRET

3. Minimal Example (Authentication Only)

import { Hono } from 'hono'
import { authGuard } from '@chrislyons-dev/flarelette-hono'
import type { HonoEnv } from '@chrislyons-dev/flarelette-hono'

const app = new Hono<HonoEnv>()

// Public endpoint (no auth required)
app.get('/health', (c) => c.json({ ok: true }))

// Protected endpoint (requires valid JWT)
app.get('/protected', authGuard(), async (c) => {
  const auth = c.get('auth')
  return c.json({ message: 'Hello', user: auth.sub })
})

export default app

4. Example with Policies (Authorization)

Once you understand basic authentication, add role-based access control:

import { authGuard, policy } from '@chrislyons-dev/flarelette-hono'

// Define a policy
const analystPolicy = policy().rolesAny('analyst', 'admin').needAll('read:reports')

// Protect a route with policy
app.get('/reports', authGuard(analystPolicy), async (c) => {
  const auth = c.get('auth')
  return c.json({ ok: true, sub: auth.sub, roles: auth.roles })
})

See API Design for complete policy builder reference.

5. Input Validation (Required for Security)

Input validation is a critical security boundary. All endpoints that accept input must validate it.

We strongly recommend using Zod for type-safe runtime validation:

npm install zod @hono/zod-validator

Combine authentication + validation:

import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'

// Define validation schema
const createUserSchema = z.object({
  email: z.string().email(),
  name: z.string().min(1).max(100),
  age: z.number().int().positive().optional(),
})

// Apply auth + validation middleware
app.post(
  '/users',
  authGuard(policy().rolesAny('admin')), // JWT auth + policy
  zValidator('json', createUserSchema), // Input validation
  async (c) => {
    const auth = c.get('auth') // Typed JWT payload
    const body = c.req.valid('json') // Typed validated input

    return c.json({ ok: true })
  }
)

See Input Validation Guide for complete security best practices.

6. Structured Logging (Optional, Recommended for Production)

Structured logging ensures consistency across polyglot microservices (ADR-0013 compliance).

Install logging dependencies:

npm install hono-pino pino

Add structured logging middleware:

import { createLogger } from '@chrislyons-dev/flarelette-hono'

// Add logging middleware (before auth)
app.use('*', createLogger({ service: 'bond-valuation' }))
app.use('*', authGuard())

app.post('/calculate', async (c) => {
  const logger = c.get('logger')
  const auth = c.get('auth')

  // Structured logging with context
  logger.info({ userId: auth.sub, operation: 'calculate' }, 'Processing calculation')

  return c.json({ result: 42 })
})

Output (JSON):

{
  "timestamp": "2025-11-02T12:34:56.789Z",
  "level": "info",
  "service": "bond-valuation",
  "requestId": "a3f2c1b0-1234-5678-9abc",
  "userId": "auth0|123",
  "operation": "calculate",
  "msg": "Processing calculation"
}

See Structured Logging Guide for ADR-0013 standards and best practices.

7. Test Your Setup

Start development server:

wrangler dev

Test protected endpoint:

# This will return 401 (expected - no JWT)
curl http://localhost:8787/protected

# Generate test JWT and make authenticated request
# (See examples/ directory for token generation utilities)

8. Wrangler Config Example

name = "bond-consumer"
main = "src/app.ts"
compatibility_date = "2025-10-01"

[[services]]
binding = "GATEWAY"
service = "bond-gateway"

[vars]
JWT_ISS = "https://gateway.internal"
JWT_AUD = "bond-math.api"

Explicit Configuration (New!)

The traditional authGuard() middleware reads JWT configuration from environment variables (JWT_ISS, JWT_AUD, JWT_SECRET_NAME, etc.). This works well for most cases but can be challenging for:

  • Testing: Setting up environment variables for unit tests
  • Multi-tenant apps: Different JWT configs for different routes
  • Development: Avoiding global state mutation

Solution: Use authGuardWithConfig() with explicit configuration objects:

import { authGuardWithConfig, createHS512Config } from '@chrislyons-dev/flarelette-hono'

// Create explicit JWT configuration
const config = createHS512Config(
  'your-base64url-encoded-secret', // Base64url-encoded secret (32+ bytes)
  {
    iss: 'https://auth.example.com',
    aud: 'api.example.com',
  }
)

// Use in middleware (no environment variables needed!)
app.get('/protected', authGuardWithConfig(config), async (c) => {
  const auth = c.get('auth')
  return c.json({ user: auth.sub })
})

// Works with policies too
const adminPolicy = policy().rolesAny('admin').build()
app.get('/admin', authGuardWithConfig(config, adminPolicy), async (c) => {
  return c.json({ admin: true })
})

Multi-Tenant Example

Different routes can use different JWT configurations in the same process:

const tenantAConfig = createHS512Config(secretA, {
  iss: 'https://auth-tenant-a.example.com',
  aud: 'tenant-a',
})

const tenantBConfig = createHS512Config(secretB, {
  iss: 'https://auth-tenant-b.example.com',
  aud: 'tenant-b',
})

app.get('/tenant-a/*', authGuardWithConfig(tenantAConfig), tenantAHandlers)
app.get('/tenant-b/*', authGuardWithConfig(tenantBConfig), tenantBHandlers)

Testing Benefits

Explicit configuration makes testing much easier:

import { createHS512Config, authGuardWithConfig } from '@chrislyons-dev/flarelette-hono'
import { SignJWT } from 'jose'

// Test setup (no environment pollution!)
const testSecret = Buffer.alloc(32, 42).toString('base64url')
const testConfig = createHS512Config(testSecret, {
  iss: 'test-issuer',
  aud: 'test-audience',
})

// Create test token
const token = await new SignJWT({ sub: 'test-user' })
  .setProtectedHeader({ alg: 'HS512' })
  .setIssuer('test-issuer')
  .setAudience('test-audience')
  .setIssuedAt()
  .setExpirationTime('1h')
  .sign(Buffer.from(testSecret, 'base64url'))

// Test authenticated request
const res = await app.request('/protected', {
  headers: { Authorization: `Bearer ${token}` },
})

When to use each approach:

  • Environment-based (authGuard): Production deployments, standard Workers with env vars
  • Explicit config (authGuardWithConfig): Testing, multi-tenant apps, development, avoiding global state

Both approaches are fully supported and can be mixed in the same application!


Gateway and Service Mesh Architecture

Typical flarelette deployments use a gateway pattern where external OIDC tokens are verified at the gateway, which then mints internal tokens for the service mesh.

Both gateway and internal services use flarelette-hono — the only difference is the JWKS resolution strategy:

| Component | JWKS Source | Purpose | |-----------|-------------|---------| | Gateway | JWT_JWKS_URL (HTTP) | Verify external OIDC tokens (Auth0, Okta, CF Access) | | Internal Services | JWT_JWKS_SERVICE_NAME (binding) | Verify internal tokens from gateway (fast RPC) |

Gateway Example

import { Hono } from 'hono'
import { authGuard } from '@chrislyons-dev/flarelette-hono'
import { sign } from '@chrislyons-dev/flarelette-jwt'
import type { HonoEnv } from '@chrislyons-dev/flarelette-hono'

const app = new Hono<HonoEnv>()

// Verify external OIDC tokens via JWT_JWKS_URL
app.post('/token-exchange', authGuard(), async (c) => {
  const externalAuth = c.get('auth')

  // Mint internal token for service mesh
  const internalToken = await sign({
    sub: externalAuth.sub,
    email: externalAuth.email,
    roles: deriveRoles(externalAuth),
    permissions: derivePermissions(externalAuth)
  })

  return c.json({ token: internalToken })
})
# Gateway wrangler.toml
[vars]
JWT_ISS = "https://auth0.example.com/"
JWT_AUD = "my-app-client-id"
JWT_JWKS_URL = "https://auth0.example.com/.well-known/jwks.json"

# For minting internal tokens
JWT_PRIVATE_JWK_NAME = "GATEWAY_PRIVATE_KEY"

Internal Service Example

import { Hono } from 'hono'
import { authGuard } from '@chrislyons-dev/flarelette-hono'
import type { HonoEnv } from '@chrislyons-dev/flarelette-hono'

const app = new Hono<HonoEnv>()

// Verify internal tokens via service binding
app.get('/data', authGuard(), async (c) => {
  const auth = c.get('auth')
  return c.json({ data: [], user: auth.sub })
})
# Service wrangler.toml
[[services]]
binding = "GATEWAY"
service = "jwt-gateway"

[vars]
JWT_ISS = "https://gateway.internal"
JWT_AUD = "bond-math.api"
JWT_JWKS_SERVICE_NAME = "GATEWAY"

See Configuration Guide for complete examples.


Cloudflare Access Integration

Workers behind Cloudflare Access can authenticate users via the CF-Access-Jwt-Assertion header. This header contains a JWT issued by Cloudflare after successful authentication.

Note: Cloudflare Access uses standard JWKS format (RFC 7517) but with a non-standard path (/cdn-cgi/access/certs). It does not provide an OIDC discovery endpoint. This is fine — authGuard() supports direct JWKS URLs.

How It Works

Cloudflare Access acts as an authentication layer before requests reach your Worker:

  1. User authenticates via Access (SSO, OIDC, etc.)
  2. Cloudflare injects CF-Access-Jwt-Assertion header with signed JWT
  3. Your Worker validates the JWT using authGuard()

No code changes requiredauthGuard() automatically checks both Authorization and CF-Access-Jwt-Assertion headers.

Header Precedence

When both headers are present, Authorization takes priority:

// Authorization header is checked first
// CF-Access-Jwt-Assertion is used as fallback if Authorization is missing or invalid

This allows you to:

  • Use Cloudflare Access for browser-based users (CF-Access-Jwt-Assertion)
  • Use API tokens for service-to-service calls (Authorization: Bearer)

Configuration

Configure authGuard() to verify Cloudflare Access JWTs:

import { authGuard } from '@chrislyons-dev/flarelette-hono'
import type { HonoEnv } from '@chrislyons-dev/flarelette-hono'

const app = new Hono<HonoEnv>()

// Works with both Authorization and CF-Access-Jwt-Assertion headers
app.get('/protected', authGuard(), async (c) => {
  const auth = c.get('auth')
  return c.json({ user: auth.sub, email: auth.email })
})

Environment variables:

# wrangler.toml
[vars]
JWT_ISS = "https://your-team.cloudflareaccess.com"  # Cloudflare Access issuer
JWT_AUD = "your-application-aud-tag"                # Application audience tag

Get your team domain and audience tag from the Cloudflare Zero Trust dashboard.

Verify Cloudflare Access Tokens

Set up JWKS verification for Cloudflare Access public keys:

# wrangler.toml
[vars]
JWT_ISS = "https://your-team.cloudflareaccess.com"
JWT_AUD = "your-application-aud-tag"

# JWKS URL is a public endpoint - no need for secret
JWT_JWKS_URL = "https://your-team.cloudflareaccess.com/cdn-cgi/access/certs"

Security note: Cloudflare Access uses short-lived JWTs (typically 1 hour). Set appropriate JWT_LEEWAY to handle clock skew.

Mixed Authentication Example

Support both Cloudflare Access (browser) and API tokens (service-to-service):

// Browser users → Cloudflare Access → CF-Access-Jwt-Assertion header
// API clients → Direct token → Authorization: Bearer header

app.get('/data', authGuard(), async (c) => {
  const auth = c.get('auth')

  // Both authentication methods provide the same JwtPayload structure
  return c.json({
    user: auth.sub,
    email: auth.email,
    source: c.req.header('CF-Access-Jwt-Assertion') ? 'access' : 'api'
  })
})

Limitations

  • Same JWT structure required: Both authentication methods must use compatible JWT payloads
  • Single issuer: JWT_ISS applies to both Authorization and CF-Access tokens
  • No automatic header selection: Use header precedence (Authorization first) rather than route-based selection

For separate issuers or different JWT structures, use authGuardWithConfig() on different route groups.


Next Steps

  • Basic usage: See examples above for authentication and policies
  • Advanced authorization: Read API Design for complex policies
  • Production deployment: Review JWT Integration for EdDSA setup
  • Testing: See CONTRIBUTING.md for test patterns

API Summary

authGuard(policy?)

Hono middleware that:

  • extracts JWT from Authorization: Bearer <jwt> or CF-Access-Jwt-Assertion header
  • validates via jwt-kit
  • enforces the given policy (if provided)
  • injects the verified claims into c.set('auth', payload)

Header Precedence: Authorization is checked first; CF-Access-Jwt-Assertion is used as fallback.

If validation fails → returns 401 Unauthorized or 403 Forbidden.

import { authGuard, policy } from '@chrislyons-dev/flarelette-hono'

// Require authentication only
app.get('/protected', authGuard(), async (c) => {
  const auth = c.get('auth')
  return c.json({ user: auth.sub })
})

// Require authentication + policy
app.get('/admin', authGuard(policy().rolesAny('admin')), async (c) => {
  return c.json({ message: 'Admin only' })
})

policy()

Fluent builder for permission rules:

policy()
  .rolesAny('admin', 'analyst') // At least one role required
  .rolesAll('verified', 'approved') // All roles required
  .needAny('read:data', 'read:reports') // At least one permission required
  .needAll('write:reports', 'audit:log') // All permissions required

Context Helpers

import type { AuthContext } from '@chrislyons-dev/flarelette-hono'

app.get('/data', authGuard(), async (c) => {
  // Access verified JWT payload
  const auth: AuthContext = c.get('auth')

  // auth.sub: string
  // auth.iss: string
  // auth.aud: string
  // auth.roles?: string[]
  // auth.permissions?: string[]
  // auth.actor?: ActorClaim
})

Configuration (shared with jwt-kit)

| Variable | Description | | ------------------------------------------ | ------------------------------------------------------- | | JWT_ISS, JWT_AUD | Expected issuer & audience | | JWT_TTL_SECONDS, JWT_LEEWAY | Defaults: 900 / 90 | | JWT_SECRET_NAME / JWT_SECRET | HS512 secret | | JWT_PRIVATE_JWK_NAME / JWT_PRIVATE_JWK | EdDSA signing key (gateway) | | JWT_JWKS_URL | JWKS endpoint (public URL, use for OIDC/Access) | | JWT_JWKS_URL_NAME | Secret name containing JWKS URL (rarely needed) | | setJwksResolver() | For internal Service Bindings (no public JWKS endpoint) |


Documentation


Testing Tips

  • Run integration tests in Miniflare with JWT_SECRET or a stubbed resolver

  • Use @chrislyons-dev/flarelette-jwt CLI to generate 64-byte secrets for HS512

  • Mock the JWKS resolver in local tests:

    import { setJwksResolver } from '@chrislyons-dev/flarelette-jwt'
    
    setJwksResolver(async () => ({ keys: [mockPublicJwk] }))

Roadmap

  • [ ] Optional mTLS / Access integration for external JWKS
  • [ ] KV-backed replay store (jti)
  • [ ] Rich error handling hooks (onUnauthorized, onForbidden)
  • [ ] OpenAPI/Swagger integration

Security First

JWT authentication and input validation are critical security boundaries. This library prioritizes security over convenience:

Security Requirements (v1.13+)

HS512 Secret Minimum Length ⚠️

CRITICAL: As of flarelette-jwt v1.13, HS512 secrets must be at minimum 64 bytes (512 bits). Shorter secrets will cause configuration errors at startup.

Generate secure secrets:

# Required: 64-byte minimum for HS512
npx flarelette-jwt-secret --len=64

Why 64 bytes?

  • Matches SHA-512 digest size (512 bits)
  • Prevents brute-force attacks on weak secrets
  • Industry best practice for HMAC-SHA-512
  • Breaking change from v1.12 and earlier

Error if secret too short:

Error: JWT secret too short: 32 bytes, need >= 64 for HS512 (use 'npx flarelette-jwt-secret --len=64')

Mode Exclusivity (Algorithm Confusion Prevention)

CRITICAL: You cannot configure both HS512 and asymmetric (EdDSA/RSA) modes simultaneously. This prevents algorithm confusion attacks (CVE-2015-9235).

Choose ONE mode:

  • HS512: JWT_SECRET_NAME or JWT_SECRET
  • EdDSA: JWT_PRIVATE_JWK_NAME + JWT_PUBLIC_JWK_NAME
  • RSA (external OIDC): JWT_JWKS_URL
  • HS512 + EdDSA: Configuration error

Error if both configured:

Configuration error: Both HS512 (JWT_SECRET) and asymmetric (JWT_PUBLIC_JWK/JWT_JWKS_*) secrets configured. Choose one to prevent algorithm confusion attacks.

Authentication Security

  • Fail securely: Invalid tokens return 401, insufficient permissions return 403
  • No detail leakage: Error messages never expose token structure or validation details
  • Short-lived tokens: 5-15 minute TTL recommended
  • Audience validation: Prevents token reuse across services
  • Algorithm whitelisting: HS512 mode only allows ['HS512'], EdDSA/RSA mode only allows ['EdDSA', 'RS256', 'RS384', 'RS512']
  • No alg: none: The none algorithm is never supported (CVE-2015-2951)
  • JWKS injection prevention: JWKS URLs pinned in config, never read from token headers

Input Validation Security

  • Validate all input: Every endpoint that accepts data must validate it
  • Use Zod: Type-safe runtime validation prevents injection attacks and type confusion
  • Constrain everything: String lengths, array sizes, numeric ranges, formats
  • Never trust input: Even from authenticated users

Type Safety

  • 100% strict TypeScript: No any types throughout the codebase
  • Runtime + compile-time validation: Zod provides both type inference and runtime checks
  • Strong typing prevents vulnerabilities: Type confusion and injection attacks are mitigated

See JWT Integration Guide for detailed security considerations and the flarelette-jwt Security Guide for complete cryptographic details.


Contributing

Contributions are welcome! Please read CONTRIBUTING.md for:

  • Development setup
  • Code style guidelines (strict TypeScript, no any)
  • Testing requirements
  • Pull request process

License

MIT © Chris Lyons

Part of the Flarelette micro-API toolkit for Cloudflare Workers.


Related Projects