Zurück zum Blog

Folgen und abonnieren

Vereinfachte Authentifizierung mit OAuth auf der Edge

Dora Militaru

Developer Relations Engineer

Andrew Betts

Principal Developer Advocate, Fastly

Authentifizierung ist ein beängstigendes, schwieriges, gefährliches und absolut unerlässliches Thema. Sie wird für die meisten Anwendungen benötigt und ist deshalb eine Grundvoraussetzung für fast alle Endnutzeranfragen. Die perfekte Web-App-Authentifizierung sollte nah am Endnutzer stattfinden. Sie sollte vom Rest des Systems isoliert, von Sicherheitsexperten implementiert und gewartet und einfach zu integrieren sein.

Das Prinzip ist so ähnlich wie das schnelle, dezentrale, sichere und autonome OAuth in Compute@Edge. Sehen wir uns doch einmal an, wie das funktioniert.

Das Wichtigste zuerst: Die Authentifizierung dient dem Identitätsnachweis und die Autorisierung bestimmt, welche Rechte ein Nutzer hat. Sehen wir uns zunächst einmal an, wie wir die Identität eines Nutzers feststellen können. Meistens geschieht das mithilfe von OAuth 2.0 und OpenID Connect.

Wenn Anfragen auf der Edge eintreffen, müssen wir diejenigen, die einem bestimmten Nutzer zugeordnet werden können, von denen, die anonym oder ungültig sind, trennen können. Anonyme Anfragen werden durch einen OAuth Flow für Autorisierungscodes geleitet, ungültige Anfragen werden zurückgewiesen. Es werden also nur Anfragen von authentifizierten Nutzern an den Origin-Server der Anwendung weitergeleitet.

Hier der von uns erstellte Ablauf im Detail:

Sehen wir uns das einmal genauer an:

  1. Der Nutzer stellt eine Anfrage für eine geschützte Ressource, hat aber kein Sitzungscookie.

  2. Auf der Edge generiert Fastly Folgendes:

    1. Einen eindeutigen und nicht durch Zufall ermittelbaren Status-Parameter, der kodiert, was der Nutzer zu tun versucht hat (Laden von /articles/kittens).

    2. Eine zufällige, kryptografische Zeichenfolge, die als Code Verifier bezeichnet wird.

    3. Eine vom Code Verifier abgeleitete Code Challenge

    4. Ein zeitlich begrenztes, verschlüsseltes Token zur Enkodierung des Status und einer Nonce (einer zufälligen Zahlen- oder Buchstabenkombination, die in dem jeweiligen Kontext nur ein einziges Mal verwendet wird, um Replay-Angriffe zu verhindern).

    Wir speichern (a) und (b) in Cookies, damit wir sie später wieder abrufen können. Wir fügen (c) und (d) zur nächsten Anfrage an den Autorisierungsserver hinzu.

  3. Fastly erstellt eine Autorisierungs-URL und leitet den Nutzer an den vom Identity Provider (IdP) betriebenen Autorisierungsserver weiter.

  4. Der Nutzer erledigt die Anmeldeformalitäten direkt beim IdP. Fastly kommt erst ins Spiel, wenn wir eine Anfrage für eine Callback-URL mit den Ergebnissen des Anmeldevorgangs erhalten. Der IdP fügt diesem Post-Login-Callback einen Autorisierungscode und einen Status hinzu (der mit dem zeitlich begrenzten Token übereinstimmen sollte, das wir zuvor erstellt haben). 

  5. Der Edge-Service authentifiziert das vom IdP zurückgegebene Status-Token und prüft, ob der gespeicherte Status mit dem Subject Claim übereinstimmt.

  6. Fastly verbindet sich direkt mit dem IdP und tauscht den Autorisierungscode (der nur einmalig gültig ist) und den Code Verifier gegen Sicherheits-Tokens aus:

    1. Ein Access Token – einen Schlüssel, der dazu berechtigt, im Namen des Nutzers bestimmte Vorgänge auszuführen

    2. Ein ID Token, das die Profilinformationen des Nutzers enthält

  7. Fastly leitet den Endnutzer zusammen mit den in Cookies gespeicherten Sicherheits-Tokens zur ursprünglichen Anfrage-URL (/articles/kittens) weiter.

  8. Wenn der Nutzer die umgeleitete Anfrage stellt (oder auch jede nachfolgende Anfrage, die mit Sicherheits-Tokens versehen ist), verifiziert Fastly die Integrität, Gültigkeit und Claims für beide Tokens. Wenn die Tokens noch gültig sind, wird die Anfrage an Ihren Origin-Server weitergeleitet.

Bevor Sie mit der Einrichtung beginnen, benötigen Sie aber zunächst einen Identity-Provider. 

So finden Sie einen Identity-Provider (IdP)

Vielleicht haben Sie Ihren eigenen Identity-Service. Ansonsten können Sie aber auch jeden anderen OAuth 2.0-, OpenID Connect (OIDC)-konformen Anbieter nutzen. Führen Sie folgende Schritte aus, um den IdP einzurichten:

  1. Registrieren Sie Ihre Anwendung beim IdP. Notieren Sie sich die client_id und die Adresse des Autorisierungsservers.

  2. Speichern Sie eine lokale Kopie der OpenID Connect Erkennungsmetadaten, die dem Server zugeordnet sind. Diese finden Sie unter/.well-known/openid-configuration in der Domain des Autorisierungsservers. Hier zum Beispiel die von Google.

  3. Speichern Sie eine Kopie der JSON Web Key Set (JWKS) Metadaten ab. Sie finden sie unter der Funktion jwks_uri im Discovery-Metadatendokument, das Sie gerade heruntergeladen haben.

Erstellen Sie nun auf Fastly einen Compute@Edge Service, der mit dem IdP kommuniziert.

So erstellen Sie den Compute@Edge Service

Wir haben alles, was Sie für dieses Projekt benötigen, in ein Repository auf GitHub gestellt. Sie brauchen aber auch einen Fastly Account, auf dem Compute@Edge Services aktiviert sind. Wenn Sie dies nicht bereits getan haben, führen Sie das Compute@Edge Setup aus, das die Fastly CLI und die Rust Toolchain auf Ihrem lokalen Rechner installiert. Jetzt geht es an den Code!

  1. Klonen Sie das Repository:
    git clone https://github.com/fastly/compute-rust-auth

  2. undefinedBefolgen Sie die Anweisungen in der README-Datei.

Herzlichen Glückwunsch, Sie haben einen Compute@Edge Service implementiert! Um die Integration abzuschließen, muss Ihr Identity-Provider noch wissen, dass er die Nutzer nach der Anmeldung zum Compute@Edge Service weiterleiten soll.

So stellen Sie eine Verknüpfung zum Identity-Provider her

Fügen Sie https://{some-funky-words}.edgecompute.app/callback zur Liste der erlaubten Callback-URLs in der Anwendungskonfiguration Ihres IdP hinzu. Damit kann der Autorisierungsserver den Nutzer zurück zu Ihrer von Fastly gehosteten Website leiten.

Öffnen Sie Ihre Anwendung in einem Browser und lassen Sie sich überraschen!

Bei unserer Beispiel-App müssen Sie sich für jeden Pfad, den Sie öffnen möchten, authentifizieren. Wenn Sie bereits authentifiziert sind, leitet Fastly die Anfrage wie üblich an Ihren Origin-Server weiter. Hier gibt es noch einige komplexere Möglichkeiten, darüber sprechen wir aber demnächst in einem separaten Blogpost. Sehen wir uns zunächst einmal an, wie die grundlegende, minimale Integration funktioniert.

Authentifizierung und Compute@Edge

Compute@Edge Programme, die mithilfe von Fastly Rust SDK in Rust geschrieben werden, haben eine Hauptfunktion, die als Zugangspunkt für Anfragen dient. Normalerweise erhält diese Hauptfunktion eine Request-Struktur und gibt eine Response-Struktur zurück.

Als Erstes prüfen wir in der Hauptfunktion, ob es sich um einen Callback-Pfad handelt, der angibt, dass der Nutzer vom IdP authentifiziert wurde und seine Sitzung starten kann. Dieser Pfad muss grundsätzlich abgefangen werden und wird nie an das Backend weitergeleitet.

if req.get_url_str().starts_with(&redirect_uri) {

// ... snip: Validate state and code_verifier ...
// ... snip: Check authorization code with identity provider ...
// ... snip: Identity provider returns access & ID tokens ...

Ok(responses::temporary_redirect(
        original_req,
        cookies::session("access_token", &auth.access_token),
        cookies::session("id_token", &auth.id_token),
        cookies::expired("code_verifier"),
        cookies::expired("state"),
    ))
}

Durch eine zufriedenstellende Antwort des IdP werden der Autorisierungscode und der Proof Key ungültig (erinnern Sie sich noch an den Code Verifier und die Code Challenge?) und es wird ein access_token und ein id_token zurückgegeben:

  • Dieses Access Token ist ein Bearer Token, das heißt, dass sein „Inhaber“ berechtigt ist, im Namen des Nutzers zu handeln und auf autorisierte Ressourcen zuzugreifen. 

  • Ein ID Token ist ein JSON Web Token (JWT), das Identitätsinformationen über den Nutzer verschlüsselt.

Diese Tokens werden von uns verwendet, um zukünftige Anfragen zu authentifizieren. Wir speichern sie also in Cookies ab und löschen alle temporären Cookies, die wir für den Authentifizierungsprozess verwendet haben (erinnern Sie sich noch an den Status-Parameter und den Code Verifier?). Anschließend leiten wir den Nutzer zurück zu der ursprünglich angefragten URL.

Wenn wir wissen, dass der Nutzer nicht auf dem Callback-Pfad ist, suchen wir nach zur Anfrage gehörenden Cookies, um (anhand des Access Tokens und des ID Tokens) zu ermitteln, ob es sich um eine aktive Sitzung handelt:

let cookie = cookies::parse(req.get_header_str("cookie").unwrap_or(""));
if let (Some(access_token), Some(id_token)) = (cookie.get("access_token"), cookie.get("id_token")) {

    // snip: ... validation logic ...

    req.set_header("access-token", access_token);
    req.set_header("id-token", id_token);

    return Ok(req.send("backend")?);
}

Wenn die Sitzung bereits läuft und gültig ist, sendet req.send() die Anfrage upstream an Ihren eigenen Origin-Server und schickt eine Antwort zurück, die downstream an den Client gesendet werden kann. Unser Beispielservice fügt das Access Token und das ID Token in nutzerdefinierte HTTP-Header ein, die zum Origin-Server weitergeleitet werden. Je nachdem, für welche Identity-Lösung am Origin-Server Sie sich entscheiden, können Sie aber auch anders vorgehen – mehr dazu gleich!

Und falls der Nutzer weder authentifiziert noch im Begriff ist sich anzumelden, leiten wir den Prozess ein, indem wir ihn zum Identity-Provider weiterleiten, um sich anzumelden:

let authorize_req =
Request::get(settings.openid_configuration.authorization_endpoint)
  .with_query(&AuthCodePayload {
     client_id: &settings.config.client_id,
      code_challenge: &pkce.code_challenge,
      code_challenge_method: &settings.config.code_challenge_method,
      redirect_uri: &redirect_uri,
      response_type: "code",
      scope: &settings.config.scope,
      state: &state_and_nonce,
      nonce: &nonce,
  })
  .unwrap();

Ok(responses::temporary_redirect(
    authorize_req.get_url_str(),
    cookies::expired("access_token"),
    cookies::expired("id_token"),
    cookies::session("code_verifier", &pkce.code_verifier),
    cookies::session("state", &state),
))

Wir verwenden Request::get, um eine Anfrage zu erstellen, aber anstatt sie zu senden, extrahieren wir die serialisierte URL und leiten den Nutzer auf diese um. Außerdem speichern wir den Status-Parameter und den Code Verifier, damit wir sie später überprüfen können, und löschen alle Access und ID Tokens, die von unserem vorherigen Code abgelehnt wurden.

Dies sind die drei Methoden, wie unsere Authentifizierungslösung mit eingehenden Anfragen interagiert.

Zusammenfassung

Das Grundkonzept, eine Authentifizierung durchzuführen und anschließend anhand von Identitätsdaten Autorisierungsentscheidungen zu treffen, gilt für die meisten Web- und nativen Anwendungen. Die entsprechenden Abläufe auf die Edge zu verlagern, kann klare Vorteile für Entwickler und Endnutzer bieten.

  • Mehr Sicherheit, da der Authentifizierungsprozess nur ein einziges Mal einheitlich implementiert wird und für alle Backend-Anwendungen gilt

  • Geringerer Wartungsaufwand, da die Komponenten nicht miteinander verbunden sind

  • Besserer Datenschutz, da wir Nutzerdaten nur dort verarbeiten, wo sie benötigt werden, und nur ein Minimum an Daten mit den Anwendungen des Origin-Servers austauschen

  • Bessere Performance, da wir Inhalte auf der Edge cachen können, für deren Zugriff eine Authentifizierung erforderlich ist, und Anfragen, die sich auf den Authentifizierungsprozess beziehen, direkt auf der Edge beantwortet werden

Dies ist nur eine von vielen Anwendungsmöglichkeiten von Edge Computing und ein gutes Beispiel dafür, wie die Statusprüfung allmählich aus der Kerninfrastruktur ausgelagert wird. Ein Großteil des Problems bei der Skalierung dezentraler Systeme besteht darin, die Statusprüfung kurzlebiger oder asynchroner zu gestalten. Andrew hat vor Kurzem weitere Ideen für Edge-native Apps geteilt.

Wir sind gespannt, was die Leute sonst noch alles mit Compute@Edge machen werden!