Skip to content

Latest commit

 

History

History
269 lines (192 loc) · 7.26 KB

File metadata and controls

269 lines (192 loc) · 7.26 KB

Implementation Summary: Explicit Configuration API

Problem Statement

The flarelette-jwt-kit library had complex environment detection that made it difficult to use in development:

  1. Global State Dependency: All functions read from globalThis.__FLARELETTE_ENV or process.env
  2. Miniflare Complexity: Required bindEnv() middleware to mutate global state before JWT operations
  3. No Explicit Config: Impossible to pass configuration directly as parameters
  4. Testing Pain: Hard to write isolated tests without environment pollution

This led to developers bypassing the library entirely and implementing JWT signing directly with Web Crypto API (like in bond-math).

Solution: Dual API Approach

Added a new explicit configuration API that allows passing config objects directly, while keeping the existing environment-based API for backward compatibility.

Environment-Based API (Original)

// Requires environment variables or bindEnv() middleware
import { sign, verify } from '@chrislyons-dev/flarelette-jwt'

const token = await sign({ sub: 'user123' })
const payload = await verify(token)

Use when:

  • Production deployment with Cloudflare bindings
  • Environment-based configuration is desired
  • Zero configuration code needed

Explicit Configuration API (New) ✨

// No environment variables required!
import {
  signWithConfig,
  verifyWithConfig,
  createHS512Config,
} from '@chrislyons-dev/flarelette-jwt'

const config = createHS512Config('secret', {
  iss: 'https://gateway.example.com',
  aud: 'api.example.com',
})

const token = await signWithConfig({ sub: 'user123' }, config)
const payload = await verifyWithConfig(token, config)

Use when:

  • Development environment setup
  • Testing without environment pollution
  • Multiple JWT configurations needed
  • Explicit control over every parameter

What Was Added

1. Core Module: src/explicit.ts

Configuration Types:

  • BaseJwtConfig - Shared config (iss, aud, ttlSeconds, leeway)
  • HS512Config - Symmetric (shared secret) configuration
  • EdDSASignConfig - Asymmetric signing configuration
  • EdDSAVerifyConfig - Asymmetric verification configuration

Core Functions:

  • signWithConfig() - Sign JWT with explicit config
  • verifyWithConfig() - Verify JWT with explicit config

High-Level Functions:

  • createTokenWithConfig() - Convenience wrapper for signing
  • createDelegatedTokenWithConfig() - RFC 8693 service delegation
  • checkAuthWithConfig() - Verify + authorize with policies

Helper Functions:

  • createHS512Config() - Build HS512 config from base64url secret
  • createEdDSASignConfig() - Build EdDSA sign config from JWK
  • createEdDSAVerifyConfig() - Build EdDSA verify config from JWK

2. Updated Exports: src/index.ts

Added exports for all new explicit API functions and types.

3. Comprehensive Tests: tests/explicit.test.ts

25 tests covering:

  • Token signing with explicit config
  • Token verification with explicit config
  • Delegation patterns (RFC 8693)
  • Authorization policies
  • Error cases (wrong issuer, audience, secret, expired tokens)
  • Isolation (no environment pollution, multiple configs)

All tests pass! ✓

4. Documentation

New Guide: docs/user-guide/explicit-config.md

  • Complete API reference
  • Use case examples
  • Migration guide
  • Best practices

Updated: README.md

  • Added "Two APIs: Choose Your Style" section
  • Highlighted new explicit config option
  • Added link to new documentation

Example: examples/explicit-config-example.ts

  • HS512 example
  • EdDSA example
  • Service delegation example
  • Development environment setup
  • Testing example

Benefits

For Development

Before:

// Required .env file with JWT_SECRET_NAME, JWT_ISS, JWT_AUD
// Required bindEnv() middleware
// Hard to pass different configs to different services

After:

// No .env file needed!
const config = {
  alg: 'HS512' as const,
  secret: new Uint8Array(32), // Simple dev secret
  iss: 'http://localhost:3000',
  aud: ['http://localhost:3001', 'http://localhost:3002'],
}

const token = await signWithConfig({ sub: 'dev-user' }, config)

For Testing

Before:

// Tests polluted environment variables
// Hard to isolate tests
// Mock process.env needed

After:

describe('JWT tests', () => {
  const testConfig = {
    alg: 'HS512' as const,
    secret: new Uint8Array(32),
    iss: 'test-issuer',
    aud: 'test-audience',
  }

  it('should work without env vars', async () => {
    const token = await signWithConfig({ sub: 'test' }, testConfig)
    const payload = await verifyWithConfig(token, testConfig)
    expect(payload?.sub).toBe('test')
  })
})

For Multi-Tenant Apps

Before:

// Difficult to use different configs per tenant
// Global state made this essentially impossible

After:

const tenantConfigs = new Map<string, HS512Config>()
tenantConfigs.set('tenant-a', createHS512Config(secretA, { ... }))
tenantConfigs.set('tenant-b', createHS512Config(secretB, { ... }))

const config = tenantConfigs.get(tenantId)
const token = await signWithConfig(claims, config)

Backward Compatibility

100% backward compatible

  • Existing environment-based API unchanged
  • No breaking changes to existing code
  • Both APIs can be used simultaneously

Code Quality

  • ✅ All tests pass (151 passed)
  • ✅ No TypeScript errors
  • ✅ Comprehensive JSDoc comments
  • ✅ Complete type safety
  • ✅ Zero environment dependencies in explicit API

Usage in flarelette-demo

The explicit API can now be used in the gateway auth.ts:

import { signWithConfig, createHS512Config } from '@chrislyons-dev/flarelette-jwt'

// Create config once at startup
const jwtConfig = createHS512Config(env.JWT_SECRET, {
  iss: 'http://localhost:8787',
  aud: ['http://localhost:8788', 'http://localhost:8789'],
  ttlSeconds: 900,
})

// Use in auth endpoint
export async function mintToken(c: Context) {
  // No need for bindEnv() middleware!
  const token = await signWithConfig(
    {
      sub: 'user123',
      permissions: ['read:data'],
    },
    jwtConfig
  )

  return c.json({ token })
}

Next Steps

  1. Update flarelette-demo to use explicit API
  2. Consider adding EdDSA examples with real key generation
  3. Update flarelette-hono to support explicit config
  4. Add Python equivalent of explicit API
  5. Publish new version (v1.9.0)

Files Changed

  • Added: packages/flarelette-jwt-ts/src/explicit.ts (489 lines)
  • Added: packages/flarelette-jwt-ts/tests/explicit.test.ts (470 lines)
  • Added: docs/user-guide/explicit-config.md (complete guide)
  • Added: examples/explicit-config-example.ts (example code)
  • 📝 Updated: packages/flarelette-jwt-ts/src/index.ts (added exports)
  • 📝 Updated: README.md (added new API section)

Impact

This change makes flarelette-jwt-kit significantly more developer-friendly while maintaining all production capabilities. Developers no longer need to bypass the library or struggle with environment setup complexity.

The explicit API provides a clear, testable, explicit alternative to the environment-based approach, making the library suitable for a wider range of use cases.