Die Entwicklung einer Authentifizierungsbibliothek für Cloudflare Workers beginnt mit einer grundlegenden Erkenntnis: Node.js-spezifische Funktionen wie Buffer, crypto.randomBytes oder process.env sind auf der Edge-Plattform nicht verfügbar. Während Serverless-Umgebungen wie Vercel, AWS Lambda oder Fly diese APIs emulieren, setzen Workers auf ein anderes Ökosystem – die Web-APIs. Wer eine Bibliothek wie KavachOS, die ursprünglich für Node.js entwickelt wurde, auf Workers portieren möchte, muss sich auf tiefgreifende Änderungen einstellen.
Warum der Wechsel zu Workers lohnt
Cloudflare Workers bieten nicht nur Kosteneffizienz, sondern auch minimale Latenzzeiten. Mit Kaltstartzeiten von unter fünf Millisekunden eignen sie sich ideal für Authentifizierungslogik, die bei jedem autorisierten Request ausgeführt wird. Eine langsame Auth-Bibliothek kann selbst bei schnellen Backend-Antworten das Nutzererlebnis beeinträchtigen. Zudem gewinnt die Edge-Architektur bei KI-Agenten-Infrastrukturen an Bedeutung. MCP-Server, Agent-Runtimes und Funktionsaufrufe profitieren von der Nähe zum Nutzer. Eine Auth-Bibliothek, die nicht auf Workers läuft, wird schnell zum Flaschenhals in dieser dezentralen Landschaft.
Der Verlust von Buffer und die Suche nach Alternativen
Die größte Hürde bei der Migration war der Verzicht auf das Buffer-Objekt, das in der ursprünglichen Codebasis über 60 Mal verwendet wurde. Buffer ist ein reines Node.js-Konstrukt und in Workers nicht vorhanden. Stattdessen musste auf Uint8Array zurückgegriffen werden, was zu mehr Codezeilen führte:
// Vorher (Node.js)
const bytes = Buffer.from(secret, "utf-8");
const hex = bytes.toString("hex");
// Nachher (Workers)
const bytes = new TextEncoder().encode(secret);
const hex = Array.from(bytes)
.map((b) => b.toString(16).padStart(2, "0"))
.join("");Für Base64- und Base64URL-Kodierungen wurde eine Hilfsfunktion eingeführt, die Uint8Array verarbeitet und die Umwandlung in ein URL-sicheres Format ermöglicht:
function bytesToBase64Url(bytes: Uint8Array): string {
let binary = "";
for (const b of bytes) binary += String.fromCharCode(b);
return btoa(binary)
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
}Diese Funktion ersetzte alle Aufrufe von Buffer.from(bytes).toString("base64url").
Von Node Crypto zu Web Crypto: Asynchronität als Herausforderung
Ein weiterer zentraler Baustein der Migration war die Ablösung des Node.js-spezifischen crypto-Moduls durch die Web Crypto API. Während Node.js synchrone Methoden wie createHash bereitstellt, arbeitet Web Crypto ausschließlich asynchron. Das hatte weitreichende Folgen für die Codebasis:
// Vorher (Node.js)
import { randomBytes, createHash } from "node:crypto";
const token = randomBytes(32).toString("hex");
const hash = createHash("sha256").update(input).digest("hex");
// Nachher (Workers)
const bytes = new Uint8Array(32);
crypto.getRandomValues(bytes);
const token = bytesToHex(bytes);
const data = new TextEncoder().encode(input);
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
const hash = bytesToHex(new Uint8Array(hashBuffer));Die Umstellung erforderte, dass etwa 20 interne Hilfsfunktionen auf asynchrone Ausführung umgestellt wurden. Zudem wurde die JWT-Signatur von der Node.js-Bibliothek jsonwebtoken auf jose migriert, die nativ Web Crypto unterstützt. Beide Bibliotheken bieten ähnliche API-Schnittstellen, doch jose läuft ohne Polyfills auf Workers, Bun und Deno.
SQLite wird zu Cloudflare D1: Neue API, neue Syntax
Cloudflare D1 ist zwar eine SQLite-Datenbank, aber die API unterscheidet sich deutlich von der gewohnten Node.js-Implementierung. Statt einer URL wird ein binding an die Datenbank übergeben:
const kavach = await createKavach({
database: {
provider: "d1",
binding: env.DB, // Wird zur Laufzeit injiziert
},
});Um Kompatibilität zu wahren, wurde ein neuer d1-Provider eingeführt, der neben dem bestehenden sqlite-Provider fungiert. Während better-sqlite3 die Syntax db.prepare(sql).run(...) nutzt, verwendet D1 die Kette db.prepare(sql).bind(...).run(). Die Migrationstools mussten daher zwei Pfade unterstützen: Auf Node.js wird das Schema aus einer Datei gelesen, während es auf Workers zur Build-Zeit als String eingebunden wird.
process.env existiert nicht mehr – Bindings übernehmen die Rolle
In Workers gibt es kein process.env. Stattdessen werden Umgebungsvariablen und Konfigurationen als env-Objekt an den Request-Handler übergeben:
export default {
async fetch(request: Request, env: Env) {
const kavach = await createKavach({
secret: env.AUTH_SECRET,
database: {
provider: "d1",
binding: env.DB,
},
});
return kavach.handle(request);
},
};Dies erforderte, dass alle Zugriffe auf Umgebungsvariablen zentral in einem Konfigurationsobjekt gebündelt wurden. Diese Änderung vereinfachte nicht nur die Testumgebung, sondern machte die Bibliothek auch robuster gegenüber versehentlichen Abhängigkeiten von externen Variablen.
TypeScript 5.8 und die Herausforderung der Typisierung
Ein weniger offensichtlicher Stolperstein war die Typisierung in TypeScript 5.8. Methoden wie crypto.subtle.digest geben einen ArrayBuffer zurück, der nicht direkt in ein Uint8Array umgewandelt werden kann. Der folgende Code führte zu Kompilierungsfehlern:
const bytes: Uint8Array = await crypto.subtle.digest("SHA-256", data);Die Lösung bestand darin, den ArrayBuffer explizit in ein Uint8Array zu wrappen:
const bytes = new Uint8Array(await crypto.subtle.digest("SHA-256", data));Insgesamt mussten 30 Stellen in der Codebasis angepasst werden, um diese Typisierungsregeln zu erfüllen.
Was die Migration gebracht hat – und was man besser vermeidet
Nach der Portierung sank die Bundle-Größe der Bibliothek von 240 KB auf 90 KB, da Node.js-spezifische Abhängigkeiten entfielen. Die Kaltstartzeit auf Workers beträgt nun etwa vier Millisekunden für einen typisierten Handler. Die Bibliothek läuft nun auf Workers, Bun, Deno und Node.js – ohne Polyfills und aus einem einzigen Quellcode. Diese Konsistenz ist besonders wertvoll, da sie auch in Umgebungen wie Vercel Edge Runtime, AWS Lambda@Edge, Netlify Edge und Deno Deploy funktioniert.
Einige Arbeitsschritte hätten sich bei einer erneuten Migration vermeiden lassen. So wurde zunächst eine eigene AES-GCM-Implementierung entwickelt, bevor festgestellt wurde, dass Web Crypto bereits eine native Lösung bietet. Ein Blick in die offiziellen API-Dokumente von Web Crypto hätte hier Zeit gespart. Zudem wurde eine eigene bytesToBase64-Funktion geschrieben, obwohl btoa für ASCII-kodierte Daten ausreicht. Die Plattform-APIs sollten immer Vorrang vor Eigenimplementierungen haben.
Wer eine Node.js-Bibliothek auf Workers oder Bun portieren möchte, findet in der Open-Source-Bibliothek KavachOS ein gutes Referenzprojekt. Der Pull Request zur D1-Unterstützung ist kurz und übersichtlich, während der Wechsel von node:crypto zu Web Crypto zwar umfangreicher, aber gut dokumentiert ist.
Die Migration einer Auth-Bibliothek auf die Edge-Plattform ist kein triviales Unterfangen, doch die Vorteile in puncto Performance und Kompatibilität überwiegen die Herausforderungen bei Weitem. Wer die API-Unterschiede zwischen Node.js und Web APIs kennt, kann seine Bibliothek zukunftssicher machen – und profitiert von einer Infrastruktur, die immer näher an den Nutzer rückt.
KI-Zusammenfassung
TypeScript tabanlı bir kimlik doğrulama kütüphanesini Node.js’den Cloudflare Workers’a taşırken karşılaşılan teknik zorluklar ve çözüm yollarını keşfedin.