|
| 1 | +# Implementation Summary: Explicit Configuration API |
| 2 | + |
| 3 | +## Problem Statement |
| 4 | + |
| 5 | +The flarelette-jwt-kit library had complex environment detection that made it difficult to use in development: |
| 6 | + |
| 7 | +1. **Global State Dependency**: All functions read from `globalThis.__FLARELETTE_ENV` or `process.env` |
| 8 | +2. **Miniflare Complexity**: Required `bindEnv()` middleware to mutate global state before JWT operations |
| 9 | +3. **No Explicit Config**: Impossible to pass configuration directly as parameters |
| 10 | +4. **Testing Pain**: Hard to write isolated tests without environment pollution |
| 11 | + |
| 12 | +This led to developers **bypassing the library entirely** and implementing JWT signing directly with Web Crypto API (like in bond-math). |
| 13 | + |
| 14 | +## Solution: Dual API Approach |
| 15 | + |
| 16 | +Added a new **explicit configuration API** that allows passing config objects directly, while keeping the existing environment-based API for backward compatibility. |
| 17 | + |
| 18 | +### Environment-Based API (Original) |
| 19 | + |
| 20 | +```typescript |
| 21 | +// Requires environment variables or bindEnv() middleware |
| 22 | +import { sign, verify } from '@chrislyons-dev/flarelette-jwt' |
| 23 | + |
| 24 | +const token = await sign({ sub: 'user123' }) |
| 25 | +const payload = await verify(token) |
| 26 | +``` |
| 27 | + |
| 28 | +**Use when:** |
| 29 | + |
| 30 | +- Production deployment with Cloudflare bindings |
| 31 | +- Environment-based configuration is desired |
| 32 | +- Zero configuration code needed |
| 33 | + |
| 34 | +### Explicit Configuration API (New) ✨ |
| 35 | + |
| 36 | +```typescript |
| 37 | +// No environment variables required! |
| 38 | +import { |
| 39 | + signWithConfig, |
| 40 | + verifyWithConfig, |
| 41 | + createHS512Config, |
| 42 | +} from '@chrislyons-dev/flarelette-jwt' |
| 43 | + |
| 44 | +const config = createHS512Config('secret', { |
| 45 | + iss: 'https://gateway.example.com', |
| 46 | + aud: 'api.example.com', |
| 47 | +}) |
| 48 | + |
| 49 | +const token = await signWithConfig({ sub: 'user123' }, config) |
| 50 | +const payload = await verifyWithConfig(token, config) |
| 51 | +``` |
| 52 | + |
| 53 | +**Use when:** |
| 54 | + |
| 55 | +- Development environment setup |
| 56 | +- Testing without environment pollution |
| 57 | +- Multiple JWT configurations needed |
| 58 | +- Explicit control over every parameter |
| 59 | + |
| 60 | +## What Was Added |
| 61 | + |
| 62 | +### 1. Core Module: `src/explicit.ts` |
| 63 | + |
| 64 | +**Configuration Types:** |
| 65 | + |
| 66 | +- `BaseJwtConfig` - Shared config (iss, aud, ttlSeconds, leeway) |
| 67 | +- `HS512Config` - Symmetric (shared secret) configuration |
| 68 | +- `EdDSASignConfig` - Asymmetric signing configuration |
| 69 | +- `EdDSAVerifyConfig` - Asymmetric verification configuration |
| 70 | + |
| 71 | +**Core Functions:** |
| 72 | + |
| 73 | +- `signWithConfig()` - Sign JWT with explicit config |
| 74 | +- `verifyWithConfig()` - Verify JWT with explicit config |
| 75 | + |
| 76 | +**High-Level Functions:** |
| 77 | + |
| 78 | +- `createTokenWithConfig()` - Convenience wrapper for signing |
| 79 | +- `createDelegatedTokenWithConfig()` - RFC 8693 service delegation |
| 80 | +- `checkAuthWithConfig()` - Verify + authorize with policies |
| 81 | + |
| 82 | +**Helper Functions:** |
| 83 | + |
| 84 | +- `createHS512Config()` - Build HS512 config from base64url secret |
| 85 | +- `createEdDSASignConfig()` - Build EdDSA sign config from JWK |
| 86 | +- `createEdDSAVerifyConfig()` - Build EdDSA verify config from JWK |
| 87 | + |
| 88 | +### 2. Updated Exports: `src/index.ts` |
| 89 | + |
| 90 | +Added exports for all new explicit API functions and types. |
| 91 | + |
| 92 | +### 3. Comprehensive Tests: `tests/explicit.test.ts` |
| 93 | + |
| 94 | +**25 tests covering:** |
| 95 | + |
| 96 | +- Token signing with explicit config |
| 97 | +- Token verification with explicit config |
| 98 | +- Delegation patterns (RFC 8693) |
| 99 | +- Authorization policies |
| 100 | +- Error cases (wrong issuer, audience, secret, expired tokens) |
| 101 | +- Isolation (no environment pollution, multiple configs) |
| 102 | + |
| 103 | +**All tests pass! ✓** |
| 104 | + |
| 105 | +### 4. Documentation |
| 106 | + |
| 107 | +**New Guide:** `docs/explicit-config.md` |
| 108 | + |
| 109 | +- Complete API reference |
| 110 | +- Use case examples |
| 111 | +- Migration guide |
| 112 | +- Best practices |
| 113 | + |
| 114 | +**Updated:** `README.md` |
| 115 | + |
| 116 | +- Added "Two APIs: Choose Your Style" section |
| 117 | +- Highlighted new explicit config option |
| 118 | +- Added link to new documentation |
| 119 | + |
| 120 | +**Example:** `examples/explicit-config-example.ts` |
| 121 | + |
| 122 | +- HS512 example |
| 123 | +- EdDSA example |
| 124 | +- Service delegation example |
| 125 | +- Development environment setup |
| 126 | +- Testing example |
| 127 | + |
| 128 | +## Benefits |
| 129 | + |
| 130 | +### For Development |
| 131 | + |
| 132 | +**Before:** |
| 133 | + |
| 134 | +```typescript |
| 135 | +// Required .env file with JWT_SECRET_NAME, JWT_ISS, JWT_AUD |
| 136 | +// Required bindEnv() middleware |
| 137 | +// Hard to pass different configs to different services |
| 138 | +``` |
| 139 | + |
| 140 | +**After:** |
| 141 | + |
| 142 | +```typescript |
| 143 | +// No .env file needed! |
| 144 | +const config = { |
| 145 | + alg: 'HS512' as const, |
| 146 | + secret: new Uint8Array(32), // Simple dev secret |
| 147 | + iss: 'http://localhost:3000', |
| 148 | + aud: ['http://localhost:3001', 'http://localhost:3002'], |
| 149 | +} |
| 150 | + |
| 151 | +const token = await signWithConfig({ sub: 'dev-user' }, config) |
| 152 | +``` |
| 153 | + |
| 154 | +### For Testing |
| 155 | + |
| 156 | +**Before:** |
| 157 | + |
| 158 | +```typescript |
| 159 | +// Tests polluted environment variables |
| 160 | +// Hard to isolate tests |
| 161 | +// Mock process.env needed |
| 162 | +``` |
| 163 | + |
| 164 | +**After:** |
| 165 | + |
| 166 | +```typescript |
| 167 | +describe('JWT tests', () => { |
| 168 | + const testConfig = { |
| 169 | + alg: 'HS512' as const, |
| 170 | + secret: new Uint8Array(32), |
| 171 | + iss: 'test-issuer', |
| 172 | + aud: 'test-audience', |
| 173 | + } |
| 174 | + |
| 175 | + it('should work without env vars', async () => { |
| 176 | + const token = await signWithConfig({ sub: 'test' }, testConfig) |
| 177 | + const payload = await verifyWithConfig(token, testConfig) |
| 178 | + expect(payload?.sub).toBe('test') |
| 179 | + }) |
| 180 | +}) |
| 181 | +``` |
| 182 | + |
| 183 | +### For Multi-Tenant Apps |
| 184 | + |
| 185 | +**Before:** |
| 186 | + |
| 187 | +```typescript |
| 188 | +// Difficult to use different configs per tenant |
| 189 | +// Global state made this essentially impossible |
| 190 | +``` |
| 191 | + |
| 192 | +**After:** |
| 193 | + |
| 194 | +```typescript |
| 195 | +const tenantConfigs = new Map<string, HS512Config>() |
| 196 | +tenantConfigs.set('tenant-a', createHS512Config(secretA, { ... })) |
| 197 | +tenantConfigs.set('tenant-b', createHS512Config(secretB, { ... })) |
| 198 | + |
| 199 | +const config = tenantConfigs.get(tenantId) |
| 200 | +const token = await signWithConfig(claims, config) |
| 201 | +``` |
| 202 | + |
| 203 | +## Backward Compatibility |
| 204 | + |
| 205 | +✅ **100% backward compatible** |
| 206 | + |
| 207 | +- Existing environment-based API unchanged |
| 208 | +- No breaking changes to existing code |
| 209 | +- Both APIs can be used simultaneously |
| 210 | + |
| 211 | +## Code Quality |
| 212 | + |
| 213 | +- ✅ All tests pass (151 passed) |
| 214 | +- ✅ No TypeScript errors |
| 215 | +- ✅ Comprehensive JSDoc comments |
| 216 | +- ✅ Complete type safety |
| 217 | +- ✅ Zero environment dependencies in explicit API |
| 218 | + |
| 219 | +## Usage in flarelette-demo |
| 220 | + |
| 221 | +The explicit API can now be used in the gateway auth.ts: |
| 222 | + |
| 223 | +```typescript |
| 224 | +import { signWithConfig, createHS512Config } from '@chrislyons-dev/flarelette-jwt' |
| 225 | + |
| 226 | +// Create config once at startup |
| 227 | +const jwtConfig = createHS512Config(env.JWT_SECRET, { |
| 228 | + iss: 'http://localhost:8787', |
| 229 | + aud: ['http://localhost:8788', 'http://localhost:8789'], |
| 230 | + ttlSeconds: 900, |
| 231 | +}) |
| 232 | + |
| 233 | +// Use in auth endpoint |
| 234 | +export async function mintToken(c: Context) { |
| 235 | + // No need for bindEnv() middleware! |
| 236 | + const token = await signWithConfig( |
| 237 | + { |
| 238 | + sub: 'user123', |
| 239 | + permissions: ['read:data'], |
| 240 | + }, |
| 241 | + jwtConfig |
| 242 | + ) |
| 243 | + |
| 244 | + return c.json({ token }) |
| 245 | +} |
| 246 | +``` |
| 247 | + |
| 248 | +## Next Steps |
| 249 | + |
| 250 | +1. **Update flarelette-demo** to use explicit API |
| 251 | +2. **Consider adding EdDSA examples** with real key generation |
| 252 | +3. **Update flarelette-hono** to support explicit config |
| 253 | +4. **Add Python equivalent** of explicit API |
| 254 | +5. **Publish new version** (v1.9.0) |
| 255 | + |
| 256 | +## Files Changed |
| 257 | + |
| 258 | +- ✨ **Added:** `packages/flarelette-jwt-ts/src/explicit.ts` (489 lines) |
| 259 | +- ✨ **Added:** `packages/flarelette-jwt-ts/tests/explicit.test.ts` (470 lines) |
| 260 | +- ✨ **Added:** `docs/explicit-config.md` (complete guide) |
| 261 | +- ✨ **Added:** `examples/explicit-config-example.ts` (example code) |
| 262 | +- 📝 **Updated:** `packages/flarelette-jwt-ts/src/index.ts` (added exports) |
| 263 | +- 📝 **Updated:** `README.md` (added new API section) |
| 264 | + |
| 265 | +## Impact |
| 266 | + |
| 267 | +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. |
| 268 | + |
| 269 | +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. |
0 commit comments