Kompetenzen Portfolio Referenzen Lebenslauf Blog Termin buchen

HTTPS für dein lokales Backend mit ngrok

Gepostet von: Felix Dziekan in: Blog am 

Ich habe an anderer Stelle argumentiert, dass du auf deinem Laptop kein HTTPS brauchen solltest. Deiner Anwendung sollte es egal sein, ob der Traffic verschlüsselt ist — das ist Sache der Infrastruktur, nicht des Codes.

Und dann bindest du einen OAuth-Provider ein, und nichts davon zählt mehr. Meta, TikTok, LinkedIn, Google — sie alle weigern sich, einen Authorization Code an localhost zurückzuschicken. Keine Ausnahmen, kein Entwicklungsmodus, kein "trag es in eine Whitelist ein". Der Provider braucht eine öffentliche HTTPS-URL, um den User zurückzuschicken, und die hat deine Maschine nicht.

Das ist also die Ausnahme, und ich löse sie mit einem Tunnel.

Was ein Tunnel eigentlich macht

ngrok baut eine ausgehende Verbindung von deiner Maschine zu ihren Servern auf und gibt dir dafür eine öffentliche HTTPS-Adresse. Alles, was auf dieser Adresse ankommt, wird durch die Verbindung an einen Port auf deiner Maschine weitergereicht.

Der Provider spricht immer nur mit der öffentlichen URL; der Tunnel trägt den Request zurück in deinen Container

Der wichtige Teil: Das ist eine ausgehende Verbindung. Du öffnest keinen Port, fasst deinen Router nicht an, und es funktioniert hinter jedem NAT. Die TLS-Terminierung passiert auf ngroks Seite, dein lokaler Server spricht also weiter einfaches HTTP und erfährt nie, dass das alles passiert — genau so soll es sein.

Zum Laufen bringen

Zwei Befehle. Einmal authentifizieren:

 

ngrok config add-authtoken YOUR_TOKEN

 

Dann auf den Port zeigen, den dein Web-Container veröffentlicht:

 

ngrok http 80

 

Du bekommst eine URL wie a1b2-93-184-216-34.ngrok-free.app, und das ist dein Backend, öffentlich über HTTPS erreichbar. Trag sie in der Developer Console des Providers als Redirect-URI ein, und der OAuth-Flow läuft.

Die vier Dinge, die dich trotzdem erwischen

1. Die URL ändert sich bei jedem Neustart — außer du holst dir deine kostenlose Static Domain

Ein einfaches ngrok http 80 gibt dir jedes Mal eine neue zufällige Subdomain. Und die Redirect-URI muss auf der Seite des Providers exakt passen — jeder Neustart bedeutet also, wieder in die Developer Console zu gehen und sie zu aktualisieren. Für jeden Provider, den du anbindest.

Seit 2023 musst du damit nicht mehr leben: Jeder ngrok-Account, auch die kostenlosen, bekommt eine reservierte Domain. Hol sie dir im Dashboard und starte den Tunnel darauf:

 

ngrok http 80 --url=https://your-name.ngrok-free.app

 

Morgen dieselbe URL — du trägst die Redirect-URI also einmal ein und fasst die Developer Console nie wieder an. Eine Domain pro kostenlosem Account ist die Grenze; sobald du zwei Tunnel gleichzeitig brauchst, wird es der bezahlte Plan.

Und die URL gehört trotzdem in die Konfiguration, nie als Konstante in den Code:

 

# .env.local
META_APP_URL=https://a1b2-93-184-216-34.ngrok-free.app/
TIKTOK_REDIRECT_URL=https://a1b2-93-184-216-34.ngrok-free.app/callbacks/tiktok

 

Mach das auch mit einer Static Domain. Die URL ist eine Eigenschaft des Tunnels auf deiner Maschine, nicht deiner Anwendung — und an dem Tag, an dem du auf das Setup einer Kollegin oder auf eine bezahlte Domain wechselst, willst du eine Zeile in .env.local ändern und nicht die Codebase durchgreppen.

2. Deine App erzeugt die falschen URLs

Der Request kommt bei deinem Server als einfaches HTTP an, mit einem Host-Header deiner ngrok-Domain. Dein Framework erzeugt absolute URLs völlig zu Recht aus dem, was es sieht — und produziert http://-Links, die der Provider dann ablehnt, oder einen Redirect, der den User komplett aus dem Tunnel wirft.

Die Lösung ist, den Forwarded-Headern zu vertrauen. In Symfony:

 

framework:
    trusted_proxies: '%env(TRUSTED_PROXIES)%'
    trusted_headers: ['x-forwarded-for', 'x-forwarded-proto', 'x-forwarded-host']

 

Dann sorgt X-Forwarded-Proto: https aus dem Tunnel dafür, dass das Framework https://-URLs erzeugt. Ohne das bekommst du einen OAuth-Flow, der mit einem Redirect-URI-Mismatch scheitert, obwohl die URI in der Console richtig aussieht — weil die, die deine App tatsächlich geschickt hat, http war.

3. Die Zwischenseite zerschießt deinen Callback

Kostenlose ngrok-Domains zeigen vor dem Weiterleiten eine Browser-Warnseite. Für einen Menschen, der einmal klickt, ist das lästig. Für einen Provider, der einen Webhook per POST schickt, bedeutet es, dass er eine HTML-Warnseite statt deines Endpunkts bekommt — und die Zustellung als fehlgeschlagen protokolliert.

Überspringen kannst du sie mit dem Header, auf den ngrok achtet:

 

curl -H "ngrok-skip-browser-warning: true" your-domain.ngrok-free.app

 

Dem Webhook eines Providers kannst du allerdings keinen Header mitgeben — dafür brauchst du eine bezahlte Domain, die die Zwischenseite gar nicht erst zeigt.

4. Es ist eine offene Tür in deine Maschine

Solange der Tunnel offen ist, erreicht jeder mit der URL deinen Entwicklungsserver. Nicht theoretisch — diese URLs werden gescannt.

Deine Entwicklungsumgebung ist das am schlechtesten verteidigte Ding, das du besitzt. Debug-Modus an, Stacktraces aktiv, ein Profiler, der deine Konfiguration ausspuckt, Testnutzer mit Passwörtern wie "test". Nichts davon sollte jemals aus dem Internet erreichbar sein.

Mach den Tunnel auf, wenn du den Callback testest. Mach ihn zu, wenn du fertig bist. Behandle ihn wie eine offene Haustür, nicht wie einen Dienst, den du betreibst.

Der Traffic-Inspector ist das eigentliche Geschenk

Der Teil, den die meisten übersehen: ngrok betreibt lokal eine Weboberfläche auf localhost, die jeden durchgelaufenen Request zeigt — komplette Header, kompletter Body, und einen Replay-Button.

Das löst das Schlimmste am OAuth-Debugging. Der Provider schickt dir den Callback einmal. Wenn dein Code darauf eine Exception wirft, ist der Request weg, und für einen neuen musst du den kompletten Autorisierungs-Flow noch mal durchklicken. Mit dem Inspector drückst du auf Replay und stehst mit exakt demselben Payload wieder an deinem Breakpoint.

Das ist auch für Webhooks generell unverschämt gut. Jeder Provider, der etwas nur einmal schickt, wird zu etwas, das du beliebig oft auf deinen Code feuern kannst.

Wofür ich es nutze

Für genau zwei Dinge: OAuth-Callbacks und Webhooks. Nichts anderes auf meiner Maschine muss öffentlich erreichbar sein, und alles andere, das danach aussieht, ist meistens ein Hinweis darauf, dass etwas von seiner Umgebung abhängt, das das nicht sollte.

Der konkrete Fall, der mich dazu gebracht hat, das sauber aufzusetzen, war die lokale Entwicklung gegen die LinkedIn-API — dasselbe Problem, plus ein paar Extra-Fallstricke, die es nur bei LinkedIn gibt.

Sie brauchen einen Senior-Entwickler, der liefert?

Über 20 Jahre. Produktionssysteme. Echte Deadlines. Ob Architekt, Entwickler oder DevOps-Engineer — ich habe alle drei Rollen übernommen, oft im selben Projekt. Sparen wir uns den Agentur-Overhead und sprechen direkt.