|
| 1 | +# GitHub Copilot Instructions |
| 2 | + |
| 3 | +## Project Overview |
| 4 | + |
| 5 | +**Flarelette JWT** is a polyglot JWT authentication toolkit with identical APIs for TypeScript and Python. Environment-driven JWT authentication for Cloudflare Workers. Like Starlette, but for the edge. |
| 6 | + |
| 7 | +**Key Principles:** |
| 8 | + |
| 9 | +- Environment-driven configuration (no config files) |
| 10 | +- Cross-language API parity (TypeScript ↔ Python) |
| 11 | +- Secret-name indirection for Cloudflare Workers bindings |
| 12 | +- Security-first design with explicit trade-offs |
| 13 | + |
| 14 | +## Architecture |
| 15 | + |
| 16 | +### Mode Detection (Automatic) |
| 17 | + |
| 18 | +- **HS512 (default):** Used when `JWT_SECRET` or `JWT_SECRET_NAME` is set |
| 19 | +- **EdDSA:** Used when `JWT_PRIVATE_JWK*` or `JWT_PUBLIC_JWK*` or `JWT_JWKS_URL*` is set |
| 20 | +- **Do not add `JWT_ALG` configuration** — mode is auto-detected from environment |
| 21 | + |
| 22 | +### Secret-Name Indirection Pattern |
| 23 | + |
| 24 | +```typescript |
| 25 | +// Instead of: JWT_SECRET=<actual-secret> |
| 26 | +// Use: JWT_SECRET_NAME=FLARELETTE_JWT_SECRET |
| 27 | +// Then: wrangler secret put FLARELETTE_JWT_SECRET |
| 28 | +``` |
| 29 | + |
| 30 | +Applies to: `JWT_SECRET_NAME`, `JWT_PRIVATE_JWK_NAME`, `JWT_PUBLIC_JWK_NAME`, `JWT_JWKS_URL_NAME` |
| 31 | + |
| 32 | +### Platform Compatibility |
| 33 | + |
| 34 | +| Feature | Node.js | CF Workers (TS) | CF Workers (Python) | |
| 35 | +| ----------------- | ------- | --------------- | --------------------- | |
| 36 | +| HS512 sign/verify | ✅ | ✅ | ✅ | |
| 37 | +| EdDSA sign | ✅ | ✅ | ❌ (use Node gateway) | |
| 38 | +| EdDSA verify | ✅ | ✅ | ✅ (inline JWK only) | |
| 39 | +| JWKS fetch | ✅ | ✅ | ❌ (inline JWK only) | |
| 40 | + |
| 41 | +**Critical:** Python cannot sign EdDSA tokens due to WebCrypto API limitations in Pyodide. |
| 42 | + |
| 43 | +## Code Guidelines |
| 44 | + |
| 45 | +### When Adding Features |
| 46 | + |
| 47 | +1. **Maintain cross-language parity:** Implement in both TypeScript and Python with identical APIs |
| 48 | +2. **Respect platform limitations:** Python cannot sign EdDSA or fetch remote JWKS |
| 49 | +3. **Use environment-driven config:** No configuration files or hardcoded defaults |
| 50 | +4. **Preserve secret-name indirection:** New secrets must support `*_NAME` pattern |
| 51 | +5. **Keep fail-silent verification:** Return `null`/`None` on errors, don't throw exceptions |
| 52 | + |
| 53 | +### TypeScript Style |
| 54 | + |
| 55 | +- Enable `strict` mode |
| 56 | +- Use explicit types for public APIs |
| 57 | +- Prefer type narrowing over type assertions |
| 58 | +- Dependencies: Only `jose` library for cryptography |
| 59 | + |
| 60 | +### Python Style |
| 61 | + |
| 62 | +- Use type hints for all public functions |
| 63 | +- Follow PEP 8 |
| 64 | +- **Zero external dependencies** (use WebCrypto via `js` module only) |
| 65 | +- All JWT operations are `async` in Python |
| 66 | + |
| 67 | +### Error Handling |
| 68 | + |
| 69 | +- **Fail-silent pattern:** Verification functions return `null`/`None` on failure |
| 70 | +- Never swallow exceptions in signing operations |
| 71 | +- Validate inputs explicitly with runtime checks |
| 72 | + |
| 73 | +## Security Model |
| 74 | + |
| 75 | +### Algorithms |
| 76 | + |
| 77 | +- **HS512:** HMAC-SHA-512 with 64-byte (512-bit) secrets — for trusted producer-consumer pairs |
| 78 | +- **EdDSA:** Ed25519 — for public verification scenarios (asymmetric trust) |
| 79 | + |
| 80 | +### Claims Validation |
| 81 | + |
| 82 | +Both implementations validate: |
| 83 | + |
| 84 | +- `iss` (issuer) — must match `JWT_ISS` |
| 85 | +- `aud` (audience) — must match `JWT_AUD` |
| 86 | +- `exp` (expiration) — with configurable leeway (default 90s) |
| 87 | +- `nbf` (not before) — if present |
| 88 | +- `iat` (issued at) — automatically set on signing |
| 89 | + |
| 90 | +## Common Patterns |
| 91 | + |
| 92 | +### Cloudflare Worker (TypeScript + Hono) |
| 93 | + |
| 94 | +```typescript |
| 95 | +import { makeKit } from '@flarelette/jwt-ts/adapters/hono' |
| 96 | + |
| 97 | +app.use('*', async (c, next) => { |
| 98 | + const jwt = makeKit(c.env) // Must inject Worker env |
| 99 | + c.set('jwt', jwt) |
| 100 | + await next() |
| 101 | +}) |
| 102 | + |
| 103 | +app.get('/secure', async c => { |
| 104 | + const jwt = c.get('jwt') |
| 105 | + const auth = await jwt.checkAuth(token, jwt.policy().rolesAny('admin').build()) |
| 106 | + if (!auth) return c.json({ error: 'Unauthorized' }, 401) |
| 107 | + return c.json({ data: 'secure' }) |
| 108 | +}) |
| 109 | +``` |
| 110 | + |
| 111 | +### Cloudflare Worker (Python) |
| 112 | + |
| 113 | +```python |
| 114 | +from flarelette_jwt import sign, verify, check_auth, policy |
| 115 | +from flarelette_jwt.adapters import apply_env_bindings |
| 116 | + |
| 117 | +async def on_fetch(request, env, ctx): |
| 118 | + apply_env_bindings(env) # Must be first — injects env into os.environ |
| 119 | + |
| 120 | + # All operations are async |
| 121 | + token = await sign({"sub": "user123", "permissions": ["read:data"]}) |
| 122 | + auth = await check_auth(token, **policy().roles_any('admin').build()) |
| 123 | + |
| 124 | + if not auth: |
| 125 | + return Response.new('Unauthorized', status=401) |
| 126 | + return Response.new(f'Hello {auth["sub"]}') |
| 127 | +``` |
| 128 | + |
| 129 | +## Documentation Standards |
| 130 | + |
| 131 | +**Target audience:** Software architects and engineers (assume technical fluency) |
| 132 | + |
| 133 | +**Tone:** Security-conscious, clear, and trustworthy — like a security engineer explaining best practices to a colleague |
| 134 | + |
| 135 | +**Style:** |
| 136 | + |
| 137 | +- Concise sections: 3–7 bullets or ≤120 words |
| 138 | +- **Exception:** Security features may expand as needed (authentication, cryptography, key management) |
| 139 | +- Explain "why" behind patterns, not just "how" |
| 140 | +- Use tables and code examples for clarity |
| 141 | +- Keep examples executable and realistic |
| 142 | + |
| 143 | +**What to avoid:** |
| 144 | + |
| 145 | +- Marketing speak ("Revolutionary!" "Easiest ever!") |
| 146 | +- Vague promises without specifics |
| 147 | +- Academic abstractions that obscure practical meaning |
| 148 | +- Downplaying security complexity |
| 149 | +- Apologetic or overly casual tone |
| 150 | + |
| 151 | +## File Structure |
| 152 | + |
| 153 | +``` |
| 154 | +flarelette-jwt-kit/ |
| 155 | +├── packages/ |
| 156 | +│ ├── flarelette-jwt-ts/src/ # TypeScript implementation |
| 157 | +│ │ ├── index.ts # Public API |
| 158 | +│ │ ├── config.ts # Mode detection, env reading |
| 159 | +│ │ ├── sign.ts # HS512 + EdDSA signing |
| 160 | +│ │ ├── verify.ts # HS512 + EdDSA verification |
| 161 | +│ │ ├── jwks.ts # Remote JWKS (5-min cooldown) |
| 162 | +│ │ ├── high.ts # createToken, checkAuth, policy |
| 163 | +│ │ └── adapters/hono.ts # Worker env injection |
| 164 | +│ └── flarelette-jwt-py/ # Python implementation (zero deps) |
| 165 | +├── examples/ # Usage examples for both languages |
| 166 | +├── notes/ |
| 167 | +│ ├── coding.md # Code quality standards |
| 168 | +│ └── tone-of-voice.md # Documentation voice guidelines |
| 169 | +└── docs/ |
| 170 | + ├── security.md # Security baseline and threat model |
| 171 | + └── usage.md # User-facing usage guide |
| 172 | +``` |
| 173 | + |
| 174 | +## Environment Variables Reference |
| 175 | + |
| 176 | +**Common:** |
| 177 | + |
| 178 | +- `JWT_ISS` — Token issuer (required) |
| 179 | +- `JWT_AUD` — Token audience (required) |
| 180 | +- `JWT_TTL_SECONDS` — Token lifetime (default: 900) |
| 181 | +- `JWT_LEEWAY` — Clock skew tolerance in seconds (default: 90) |
| 182 | + |
| 183 | +**HS512:** |
| 184 | + |
| 185 | +- `JWT_SECRET` or `JWT_SECRET_NAME` — Symmetric secret (64+ bytes, base64url) |
| 186 | + |
| 187 | +**EdDSA Producer:** |
| 188 | + |
| 189 | +- `JWT_PRIVATE_JWK` or `JWT_PRIVATE_JWK_NAME` — Ed25519 private key (JSON) |
| 190 | +- `JWT_KID` — Key ID for rotation (required) |
| 191 | + |
| 192 | +**EdDSA Consumer:** |
| 193 | + |
| 194 | +- `JWT_PUBLIC_JWK` or `JWT_PUBLIC_JWK_NAME` — Ed25519 public key (JSON, inline) |
| 195 | +- `JWT_JWKS_SERVICE` or `JWT_JWKS_SERVICE_NAME` — Service binding for JWKS (TypeScript only) |
| 196 | +- `JWT_ALLOWED_THUMBPRINTS` — Comma-separated thumbprint whitelist (optional) |
| 197 | + |
| 198 | +## Testing |
| 199 | + |
| 200 | +**Current state:** No test suite exists yet. |
| 201 | + |
| 202 | +**When implementing:** |
| 203 | + |
| 204 | +- Test both HS512 and EdDSA modes via environment injection |
| 205 | +- Verify cross-language parity (same inputs → same outputs) |
| 206 | +- Cover authorization policy evaluation (roles, permissions, predicates) |
| 207 | +- Test secret-name indirection resolution |
| 208 | +- Test JWKS caching (TypeScript only) |
| 209 | +- Validate claim enforcement |
| 210 | +- Test signature verification (positive and negative cases) |
| 211 | + |
| 212 | +## Additional Resources |
| 213 | + |
| 214 | +- **docs/security.md** — Security baseline and threat model |
| 215 | +- **docs/usage.md** — User-facing usage documentation |
0 commit comments