Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Cloudflare platform skill covering Workers, D1, R2, KV, AI, Durable Objects, and security.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/secrets-store/patterns.md
1# Patterns23## Secret Rotation45Zero-downtime rotation with versioned naming (`api_key_v1`, `api_key_v2`):67```typescript8interface Env {9PRIMARY_KEY: { get(): Promise<string> };10FALLBACK_KEY?: { get(): Promise<string> };11}1213async function fetchWithAuth(url: string, key: string) {14return fetch(url, { headers: { "Authorization": `Bearer ${key}` } });15}1617export default {18async fetch(request: Request, env: Env): Promise<Response> {19let resp = await fetchWithAuth("https://api.example.com", await env.PRIMARY_KEY.get());2021// Fallback during rotation22if (!resp.ok && env.FALLBACK_KEY) {23resp = await fetchWithAuth("https://api.example.com", await env.FALLBACK_KEY.get());24}2526return resp;27}28}29```3031Workflow: Create `api_key_v2` → add fallback binding → deploy → swap primary → deploy → remove `v1`3233## Encryption with KV3435```typescript36interface Env {37CACHE: KVNamespace;38ENCRYPTION_KEY: { get(): Promise<string> };39}4041async function encryptValue(value: string, key: string): Promise<string> {42const enc = new TextEncoder();43const keyMaterial = await crypto.subtle.importKey(44"raw", enc.encode(key), { name: "AES-GCM" }, false, ["encrypt"]45);46const iv = crypto.getRandomValues(new Uint8Array(12));47const encrypted = await crypto.subtle.encrypt(48{ name: "AES-GCM", iv }, keyMaterial, enc.encode(value)49);5051const combined = new Uint8Array(iv.length + encrypted.byteLength);52combined.set(iv);53combined.set(new Uint8Array(encrypted), iv.length);54return btoa(String.fromCharCode(...combined));55}5657export default {58async fetch(request: Request, env: Env): Promise<Response> {59const key = await env.ENCRYPTION_KEY.get();60const encrypted = await encryptValue("sensitive-data", key);61await env.CACHE.put("user:123:data", encrypted);62return Response.json({ ok: true });63}64}65```6667## HMAC Signing6869```typescript70interface Env {71HMAC_SECRET: { get(): Promise<string> };72}7374async function signRequest(data: string, secret: string): Promise<string> {75const enc = new TextEncoder();76const key = await crypto.subtle.importKey(77"raw", enc.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]78);79const sig = await crypto.subtle.sign("HMAC", key, enc.encode(data));80return btoa(String.fromCharCode(...new Uint8Array(sig)));81}8283export default {84async fetch(request: Request, env: Env): Promise<Response> {85const secret = await env.HMAC_SECRET.get();86const payload = await request.text();87const signature = await signRequest(payload, secret);88return Response.json({ signature });89}90}91```9293## Audit & Monitoring9495```typescript96export default {97async fetch(request: Request, env: Env, ctx: ExecutionContext) {98const startTime = Date.now();99try {100const apiKey = await env.API_KEY.get();101const resp = await fetch("https://api.example.com", {102headers: { "Authorization": `Bearer ${apiKey}` }103});104105ctx.waitUntil(106fetch("https://log.example.com/log", {107method: "POST",108body: JSON.stringify({109event: "secret_used",110secret_name: "API_KEY",111timestamp: new Date().toISOString(),112duration_ms: Date.now() - startTime,113success: resp.ok114})115})116);117return resp;118} catch (error) {119ctx.waitUntil(120fetch("https://log.example.com/log", {121method: "POST",122body: JSON.stringify({123event: "secret_access_failed",124secret_name: "API_KEY",125error: error instanceof Error ? error.message : "Unknown"126})127})128);129return new Response("Error", { status: 500 });130}131}132}133```134135## Migration from Worker Secrets136137Change `env.SECRET` (direct) to `await env.SECRET.get()` (async).138139Steps:1401. Create in Secrets Store: `wrangler secrets-store secret create <store-id> --name API_KEY --scopes workers --remote`1412. Add binding to `wrangler.jsonc`: `{"binding": "API_KEY", "store_id": "abc123", "secret_name": "api_key"}`1423. Update code: `const key = await env.API_KEY.get();`1434. Test staging, deploy1445. Remove old: `wrangler secret delete API_KEY`145146## Sharing Across Workers147148Same secret, different binding names:149150```jsonc151// worker-1: binding="SHARED_DB", secret_name="postgres_url"152// worker-2: binding="DB_CONN", secret_name="postgres_url"153```154155## JSON Secret Parsing156157Store structured config as JSON secrets:158159```typescript160interface Env {161DB_CONFIG: { get(): Promise<string> };162}163164interface DbConfig {165host: string;166port: number;167username: string;168password: string;169}170171export default {172async fetch(request: Request, env: Env): Promise<Response> {173try {174const configStr = await env.DB_CONFIG.get();175const config: DbConfig = JSON.parse(configStr);176177// Use parsed config178const dbUrl = `postgres://${config.username}:${config.password}@${config.host}:${config.port}`;179180return Response.json({ connected: true });181} catch (error) {182if (error instanceof SyntaxError) {183return new Response("Invalid config JSON", { status: 500 });184}185throw error;186}187}188}189```190191Store JSON secret:192193```bash194echo '{"host":"db.example.com","port":5432,"username":"app","password":"secret"}' | \195wrangler secrets-store secret create <store-id> \196--name DB_CONFIG --scopes workers --remote197```198199## Integration200201### Service Bindings202203Auth Worker signs JWT with Secrets Store; API Worker verifies via service binding.204205See: [workers](../workers/) for service binding patterns.206207See: [api.md](./api.md), [gotchas.md](./gotchas.md)208