Programmierung auf der Edge mit OAuth
Authentifizierung ist eine der naheliegendsten Anwendungen für Edge Computing. Je näher Sie am Standort Ihrer Nutzer sind und je eher Sie wissen, wer diese sind, desto leistungsstärkere Anpassungen können Sie vornehmen und desto schneller können Sie reagieren. Es gibt jedoch mehr als nur eine Möglichkeit, ein Authentifizierungsschema auf der Edge anzuwenden.
In unserem letzten Blogpost haben wir uns mit einer Referenzimplementierung für die Durchführung von OAuth auf der Edge befasst, die Ihnen Zugriff auf die Sicherheits-Tokens des aktuellen Nutzers gibt. Heute wollen wir einen Blick darauf werfen, wie Sie Ihr neues Authentifizierungs-Gateway für vier spezifische und ganz unterschiedliche Anwendungsfälle nutzen können.
Paywalls und andere fortschrittliche Autorisierungsentscheidungen
Websites treffen Autorisierungsentscheidungen manchmal anhand komplexer Daten, die auf der Edge nicht verfügbar sind. Paywalls sind ein praktisches Beispiel dafür: Vielleicht müssen Sie überprüfen, ob das Guthaben eines Nutzers ausreicht, um einen bestimmten Inhalt zu „kaufen“, aber die benötigten Informationen sind nicht in den Identitäts-Tokens des Nutzers enthalten.
Dies ist ein großartiges Anwendungsbeispiel für die Weitergabe relevanter Informationen aus dem id_token
des Nutzers an den Origin-Server in Form von zusätzlichen HTTP-Headern. Der Origin kann diese Daten anschließend verwenden, um über den Zugriff zu entscheiden.
Hier sehen Sie, wie wir die id_token
-Daten auf eine Reihe von HTTP-Anfrage-Headern verteilen können:
// Define a struct that groups together the pieces of data we care about.
#[derive(serde::Serialize, serde::Deserialize)]
struct IdTokenClaims {
uuid: String,
email: String,
country: String,
}
// Validate the ID token, and destructure the claims we defined earlier.
match validate_token_rs256::<IdTokenClaims>(id_token, &settings) {
Ok(claims) => {
// Here, claims.custom is an instance of IdTokenClaims.
req.set_header("Fastly-Auth-Uuid", claims.custom.uuid);
req.set_header("Fastly-Auth-Email", claims.custom.email);
req.set_header("Fastly-Auth-Country", claims.custom.country);
}
_ => {
return Ok(responses::unauthorized("ID token invalid."));
}
}
Wenn der Origin nun eine Zugriffsentscheidung anhand bestimmter Profildaten treffen muss, kann er mit einem Vary-Header antworten, der Fastly anweist, die Antwort nur für Nutzer mit demselben Profilstatus zu cachen:
Vary: Fastly-Auth-Uuid
Wir haben schon oft über die Verwendung des Vary-Headers geschrieben (unser Kollege Doc im Jahr 2014 und Andrew erst vor Kurzem): Es handelt sich dabei um einen leistungsstarken Mechanismus, der, wenn er gut eingesetzt wird, die Cache-Performance auf der Edge enorm steigern kann.
Granulare Zugriffskontrolle für statische Inhalte
Angenommen, Ihr Content wird in einem statischen Bucket wie Amazon S3 oder Google Cloud Storage gehostet. Wenn Sie möchten, dass nur manche Nutzer auf bestimmte Inhalte zugreifen können, wird es ein wenig knifflig. Wir haben bereits gezeigt, wie Compute@Edge, das zum Erstellen, Testen und Bereitstellen von Code in unserer Serverless-Compute-Umgebung verwendet wird, statische Inhalte von einem Bucket-Provider bereitstellen kann. Warum sollten wir also nicht mit Inhalten verknüpfte Informationen zusammen mit Authentifizierungsdaten auf der Edge verwenden, um Zugriffsentscheidungen zu treffen?
Fügen Sie einen
Fastly-Require-Country
HTTP-Antwort-Header zu Ihren statischen Objekten hinzu.Lesen Sie diesen Header in der Edge-Anwendung, wenn Sie das angeforderte Element vom Origin-Server herunterladen.
Vergleichen Sie den Wert mit den Daten im
id_token
des Nutzers.Wenn es keine Übereinstimmung gibt, verwerfen Sie die Content-Antwort und generieren stattdessen einen 403-Forbidden-Statuscode.
Hier sehen Sie, wie Sie das tun können, nämlich indem Sie die gleiche IdTokenClaims
-Struktur verwenden, die wir für das vorherige Beispiel definiert haben:
match validate_token_rs256::<IdTokenClaims>(id_token, &settings) {
Ok(claims) => {
let beresp = req.send("backend")?;
if claims.custom.country != beresp.get_header_str("fastly-require-country").unwrap()
{
return Ok(Response::from_status(StatusCode::FORBIDDEN));
}
return Ok(beresp);
}
// ...
}
Zugriffserweiterung durch stufenweise Autorisierung
Angenommen, Sie betreiben einen Event-Ticket-Service, bei dem Google als Identitätsanbieter (IdP) eingesetzt wird. Wenn ein Kunde nur ein Lesezeichen für Veranstaltungen setzen möchte, reicht es, zu wissen, um wen es sich bei dem Kunden handelt. Wenn er jedoch ein Event zu seinem Google Kalender hinzufügen möchte, benötigen Sie einen zusätzlichen Scope-Parameter, der Ihnen Schreibzugriff auf den Kalender gewährt. Wenn der Nutzer später eine Buchung vornehmen möchte, benötigen Sie Zugriff auf sein Wallet oder seinen Zahlungsservice, und das könnte schon wieder ein weiterer Scope-Parameter sein.
Es könnte also sinnvoll sein, während des anfänglichen Autorisierungsprozesses eine Reihe von Scope-Parametern anzufordern, insbesondere solche, die für Dinge benötigt werden, für die sich eine große Anzahl von Nutzern interessiert und die weniger sensibel sind. Es kann aber auch Vorgänge wie Zahlungen geben, die ein Origin-Server nicht ausführen können soll, wenn er nicht explizit dazu autorisiert wurde. Das ist eine gute Möglichkeit, das Prinzip des geringsten Privilegs durchzusetzen.
In diesem Fall kann der Origin das access_token
der aktuellen Sitzung verwenden, um eine stufenweise Autorisierung vom Identitätsanbieter anzufordern.
Fügen Sie in der Edge-Anwendung einen
Fastly-Access-Token
-Header zur Anforderung hinzu, damit der Origin dieses Zugriffstoken erkennen und verwenden kann.Verwenden Sie im Origin das Zugriffstoken, um Anfragen direkt an den Identitätsanbieter zu stellen, beispielsweise um eine Zahlung zu initiieren.
Wenn der IdP die Anforderung aufgrund eines unzureichenden Scope-Parameters ablehnt, passiert Folgendes:
Der Origin gibt eine 403-Fehlermeldung mit einem
Fastly-Required-Scopes
-Header an Fastly zurück.Fastly startet daraufhin einen neuen Autorisierungs-Flow, um die Tokens des Nutzers upzugraden und den neuen Scope-Parameter zuzulassen. Außerdem verwaltet es den Callback wie gewohnt, um die Sitzung des Nutzers durch eine neue zu ersetzen.
Schließlich wird der Nutzer an die URL weitergeleitet, die die erweiterte Zustimmung angefordert hat. Damit verfügt der Origin-Server über ein neues Zugriffstoken, um nun eine erfolgreiche Anfrage an den IdP zu stellen.
Auf der Edge müssen wir dann nur noch eine 403-Antwort mit einem Fastly-Required-Scopes
-Header identifizieren und den neuen Flow auslösen:
// First, let’s make the configuration object mutable.
let mut settings = Config::load();
let beresp = req.send("backend")?;
if beresp.get_status() == fastly::http::StatusCode::FORBIDDEN {
if let Some(incremental_scopes) = beresp.get_header_str("fastly-required-scopes") {
// Append the incremental scopes to the original settings.
settings.config.scope.push(' ');
settings.config.scope.push_str(incremental_scopes);
} else {
return Ok(beresp);
}
}
Blockierung böswilliger Nutzer
Es gibt zahlreiche Gründe, warum es notwendig sein kann, den Zugriff auf Ihre App durch bestimmte Nutzer zu sperren. Hierbei ist allerdings Vorsicht geboten: Langlebige Sitzungs-Token sind effizient, aber niemand möchte einen Nutzer sperren und anschließend feststellen, dass sein Sitzungs-Token noch zwei Wochen lang gültig ist und nicht gelöscht werden kann. Umgekehrt kann das Überprüfen der Sitzung jedes Nutzers vor jedem Vorgang zu einer Verlangsamung führen, und Sie sind möglicherweise nicht in der Lage, überhaupt Inhalte auf der Edge zu cachen.
Mit OAuth auf der Edge können Sie wählen, ob das access_token
bei jeder Anfrage beim Identitätsanbieter verifiziert werden soll (was in der Regel sehr schnell geht, wenn der IdP dafür optimiert ist, solche Anfragen weltweit zu bearbeiten). Sie können in diesem Fall nach wie vor die gecachten Inhalte verwenden, um die Anfrage zu erfüllen. Wenn Sie auf diese Weise den Zugriff eines Nutzers beim Identitätsanbieter widerrufen, wird er sofort gesperrt.
In unserer Beispiel-App haben wir den Echtzeit-Verifizierungsaufruf an den IdP eingefügt:
let mut userinfo_res = Request::get(settings.openid_configuration.userinfo_endpoint)
.with_header(AUTHORIZATION, format!("Bearer {}", access_token))
.send("idp")?;
if userinfo_res.get_status().is_client_error() {
return Ok(responses::unauthorized(userinfo_res.take_body()));
}
Damit wird das Problem der Sitzungsvalidierung in Echtzeit zwar an einen Identitätsanbieter delegiert, allerdings führt dies dazu, dass dieser Anbieter jeder Anfrage eine minimale Latenz hinzufügt. Hier müssen Sie also einen Kompromiss eingehen: Wenn Sie im Gegenzug zu einer leichten Verzögerung beim Widerruf einer Sitzung eine geringere Latenz bevorzugen, kann Fastly die IdP-Antworten auf der Edge für kurze Zeit cachen.
Außerdem können Sie bei uns gecachte Inhalte innerhalb von etwa 150 ms weltweit bereinigen. Wenn Sie also in der Lage sind, den Widerruf einer Sitzung beim IdP mit dem Absenden einer Purge-Anforderung an die Fastly API zu koordinieren, können Sie das Beste aus beiden Welten herausholen, indem Sie nach Möglichkeit Sitzungen im Cache validieren und gleichzeitig in Sekundenschnelle widerrufen können.
Seien Sie kreativ!
Wenn man über die Sicherheit von Webanwendungen nachdenkt, wird ein Do-it-yourself-Ansatz oft als schlechte Nachricht angesehen, wobei „Rolling your own crypto“ vielleicht die abschreckendste Idee ist, auf die ein Entwickler kommen kann.
Auch wenn es zwar Sicherheitsbereiche gibt, die man am besten robusten, kampferprobten Implementierungen überlässt, können Sie sehr wohl selbst darüber entscheiden, wie Sie Authentifizierungs- und Autorisierungsdaten zur Steuerung Ihrer Anwendung verwenden möchten. Wir haben Ihnen hier einige Ideen vorgestellt, aber es gibt noch unzählige andere Möglichkeiten, diese Komponenten im Rahmen Ihres Entscheidungsprozesses über Nutzerrechte und -berechtigungen zusammenzuführen.
Wenn Sie eine interessante Idee dazu haben, würden wir uns freuen, wenn Sie sie auf @fastly tweeten!