Freigeben über


Schützen von Zugriffstoken in einer einzelseitigen Anwendung mithilfe von Azure API Management

Azure API Management
Microsoft Entra ID

In diesem Handbuch wird gezeigt, wie Sie azure API Management verwenden, um eine zustandslose Architektur für eine JavaScript-Einzelseitenanwendung zu implementieren, die keine Token in der Browsersitzung speichert. Auf diese Weise können Zugriffstoken vor Website-Skriptingangriffen (XSS) geschützt werden und bösartigen Code nicht im Browser ausgeführt werden.

Diese Architektur verwendet API-Verwaltungs- für:

  • Implementieren Sie ein Back-Ends für Frontends Muster, das ein OAuth2-Zugriffstoken von Microsoft Entra ID abruft.
  • Verwenden Sie advanced Encryption Standard AES, um das Zugriffstoken zu verschlüsseln und zu entschlüsseln.
  • Speichern Sie das Token in einem HttpOnly Cookie.
  • Proxy für alle API-Aufrufe, die eine Autorisierung erfordern.

Da das Back-End den Tokenerwerb verarbeitet, ist in der Einzelseitenanwendung kein anderer Code oder keine andere Bibliothek wie Microsoft-Authentifizierungsbibliothek für JavaScript (MSAL.js) erforderlich. Wenn Sie dieses Design verwenden, werden keine Token in der Browsersitzung oder im lokalen Speicher gespeichert. Das Verschlüsseln und Speichern des Zugriffstokens in einem HttpOnly-Cookie trägt dazu bei, es vor XSS- Angriffen zu schützen. Das Festlegen der Bereichsdefinition an die API-Domäne und das Festlegen SameSite auf Strict stellt sicher, dass das Cookie automatisch mit allen proxybasierten API-Anforderungen gesendet wird.

Architektur

Diagramm, das eine Architektur zeigt, die keine Token im Browser speichert.

Laden Sie eine Visio-Datei dieser Architektur herunter.

Arbeitsablauf

  1. Ein Benutzer wählt Anmelden in der Einzelseitenanwendung aus.
  2. Die Einzelseitenanwendung ruft den Autorisierungscodefluss über eine Umleitung zum Microsoft Entra-Autorisierungsendpunkt auf.
  3. Benutzer authentifizieren sich selbst.
  4. Eine Autorisierungscodeflussantwort mit einem Autorisierungscode wird an den API-Verwaltungsrückrufendpunkt umgeleitet.
  5. Die API-Verwaltungsrichtlinie austauscht den Autorisierungscode für ein Zugriffstoken durch Aufrufen des Microsoft Entra-Tokenendpunkts.
  6. Die Azure API-Verwaltungsrichtlinie leitet an die Anwendung um und platziert das verschlüsselte Zugriffstoken in einem HttpOnly-Cookie.
  7. Der Benutzer ruft einen externen API-Aufruf aus der Anwendung über einen API-Verwaltungsendpunkt auf.
  8. Die API-Verwaltungsrichtlinie empfängt die API-Anforderung, entschlüsselt das Cookie und führt einen nachgeschalteten API-Aufruf durch, und fügt das Zugriffstoken als Authorization Header hinzu.

Komponenten

  • Microsoft Entra ID Identitätsdienste, einmaliges Anmelden und mehrstufige Authentifizierung über Azure-Workloads hinweg bereitstellt.
  • API Management ist eine hybride Multicloud-Verwaltungsplattform für APIs in allen Umgebungen. Die API-Verwaltung erstellt konsistente, moderne API-Gateways für vorhandene Back-End-Dienste.
  • Azure Static Web Apps ist ein Dienst, der automatisch Full-Stack-Web-Apps in Azure aus einem Code-Repository erstellt und bereitstellt. Bereitstellungen werden durch Änderungen ausgelöst, die an Anwendungsquellcode in GitHub oder in Azure DevOps-Repositorys vorgenommen wurden.

Szenariodetails

Einzelseitenanwendungen werden in JavaScript geschrieben und im Kontext eines clientseitigen Browsers ausgeführt. In dieser Implementierung können Benutzer auf jeden Code zugreifen, der im Browser ausgeführt wird. Bösartiger Code, der im Browser oder einem XSS-Angriff ausgeführt wird, kann auch auf Daten zugreifen. Auf Daten, die in der Browsersitzung oder im lokalen Speicher gespeichert sind, kann zugegriffen werden, sodass vertrauliche Daten wie Zugriffstoken zum Identitätswechsel des Benutzers verwendet werden können.

Die hier beschriebene Architektur erhöht die Sicherheit von Anwendungen, indem der Tokenerwerb und -speicher in das Back-End verschoben und ein verschlüsseltes HttpOnly-Cookie zum Speichern des Zugriffstokens verwendet wird. Zugriffstoken müssen nicht in der Browsersitzung oder im lokalen Speicher gespeichert werden, und sie können nicht von bösartigem Code aufgerufen werden, der im Browser ausgeführt wird.

In dieser Architektur behandeln API-Verwaltungsrichtlinien den Erwerb des Zugriffstokens und die Verschlüsselung und Entschlüsselung des Cookies. Richtlinien sind Auflistungen von Anweisungen, die sequenziell für die Anforderung oder Antwort einer API ausgeführt werden und aus XML-Elementen und C#-Skripts bestehen.

Durch das Speichern des Cookies in einem HttpOnly Cookie können Sie das Token vor XSS-Angriffen schützen und sicherstellen, dass es nicht von JavaScript aufgerufen werden kann. Das Festlegen des Cookies an die API-Domäne und das Festlegen SameSite auf Strict stellt sicher, dass das Cookie automatisch mit allen proxybasierten API-Anforderungen gesendet wird. Mit diesem Design kann das Zugriffstoken automatisch zum Authorization Header aller API-Aufrufe hinzugefügt werden, die vom Back-End aus der einzelseitigen Anwendung getätigt werden.

Da diese Architektur ein SameSite=Strict Cookie verwendet, muss die Domäne des API-Verwaltungsgateways mit der Domäne der Einzelseitenanwendung identisch sein. Der Grund dafür ist, dass ein Cookie nur dann an das API-Verwaltungsgateway gesendet wird, wenn die API-Anforderung von einem Standort in derselben Domäne stammt. Wenn die Domänen unterschiedlich sind, wird das Cookie nicht zur API-Anforderung hinzugefügt, und die proxybasierte API-Anforderung bleibt nicht authentifiziert.

Sie können diese Architektur konfigurieren, ohne benutzerdefinierte Domänen für die API-Verwaltungsinstanz und statische Web-App zu verwenden, aber dann müssen Sie SameSite=None für die Cookieeinstellung verwenden. Diese Implementierung führt zu einer weniger sicheren Implementierung, da das Cookie allen Anforderungen an jede Instanz des API-Verwaltungsgateways hinzugefügt wird. Weitere Informationen finden Sie unter SameSite-Cookies.

Weitere Informationen zur Verwendung benutzerdefinierter Domänen für Azure-Ressourcen finden Sie unter Benutzerdefinierte Domänen mit Azure Static Web Apps und Konfigurieren eines benutzerdefinierten Domänennamens für Ihre Azure API-Verwaltungsinstanz. Weitere Informationen zum Konfigurieren von DNS-Einträgen für benutzerdefinierte Domänen finden Sie unter Verwalten von DNS-Zonen im Azure-Portal.

Authentifizierungsfluss

Dieser Prozess verwendet den OAuth2-Autorisierungscodefluss. Um ein Zugriffstoken zu erhalten, mit dem die Einzelseitenanwendung auf die API zugreifen kann, müssen sich die Benutzer zuerst authentifizieren. Sie rufen den Authentifizierungsfluss auf, indem Sie Benutzer an den Microsoft Entra-Autorisierungsendpunkt umleiten. Sie müssen einen Umleitungs-URI in der Microsoft Entra-ID konfigurieren. Dieser Umleitungs-URI muss der API-Verwaltungsrückrufendpunkt sein. Benutzer werden aufgefordert, sich mithilfe der Microsoft Entra-ID zu authentifizieren und mit einem Autorisierungscode zurück zum API-Verwaltungsrückrufendpunkt umgeleitet zu werden. Die API-Verwaltungsrichtlinie wechselt dann den Autorisierungscode für ein Zugriffstoken durch Aufrufen des Microsoft Entra-Tokenendpunkts. Das folgende Diagramm zeigt die Abfolge von Ereignissen für diesen Fluss.

Diagramm, das den Authentifizierungsfluss zeigt.

Der Fluss enthält die folgenden Schritte:

  1. Um ein Zugriffstoken abzurufen, damit die Einzelseitenanwendung auf die API zugreifen kann, müssen sich die Benutzer zuerst authentifizieren. Benutzer rufen den Fluss auf, indem Sie eine Schaltfläche auswählen, die sie an den Microsoft Identity Platform-Autorisierungsendpunkt umleitet. Der redirect_uri wird auf den /auth/callback API-Endpunkt des API-Verwaltungsgateways festgelegt.

  2. Benutzer werden aufgefordert, sich selbst zu authentifizieren. Wenn die Authentifizierung erfolgreich ist, antwortet die Microsoft Identity Platform mit einer Umleitung.

  3. Der Browser wird an den redirect_uriumgeleitet, bei dem es sich um den API-Verwaltungsrückrufendpunkt handelt. Der Autorisierungscode wird an den Rückrufendpunkt übergeben.

  4. Die eingehende Richtlinie des Rückrufendpunkts wird aufgerufen. Die Richtlinie tauscht den Autorisierungscode für ein Zugriffstoken aus, indem sie einen Aufruf an den Microsoft Entra-Tokenendpunkt sendet. Sie übergibt die erforderlichen Informationen, z. B. die Client-ID, den geheimen Clientschlüssel und den Autorisierungscode:

    <send-request ignore-error="false" timeout="20" response-variable-name="response" mode="new">
     <set-url>https://login.microsoftonline.com/{{tenant-id}}/oauth2/v2.0/token</set-url>
     <set-method>POST</set-method>
     <set-header name="Content-Type" exists-action="override">
         <value>application/x-www-form-urlencoded</value>
     </set-header>
     <set-body>@($"grant_type=authorization_code&code={context.Request.OriginalUrl.Query.GetValueOrDefault("code")}&client_id={{client-id}}&client_secret={{client-secret}}&redirect_uri=https://{context.Request.OriginalUrl.Host}/auth/callback")</set-body>
    </send-request>
    
  5. Das Zugriffstoken wird zurückgegeben und in einer Variablen namens tokengespeichert:

    <set-variable name="token" value="@((context.Variables.GetValueOrDefault<IResponse>("response")).Body.As<JObject>())" />
    
  6. Das Zugriffstoken wird mit AES-Verschlüsselung verschlüsselt und in einer Variablen namens cookiegespeichert:

    <set-variable name="cookie" value="@{
        var rng = new RNGCryptoServiceProvider();
        var iv = new byte[16];
        rng.GetBytes(iv);
        byte[] tokenBytes = Encoding.UTF8.GetBytes((string)(context.Variables.GetValueOrDefault<JObject>("token"))["access_token"]);
        byte[] encryptedToken = tokenBytes.Encrypt("Aes", Convert.FromBase64String("{{enc-key}}"), iv);
        byte[] combinedContent = new byte[iv.Length + encryptedToken.Length];
        Array.Copy(iv, 0, combinedContent, 0, iv.Length);
        Array.Copy(encryptedToken, 0, combinedContent, iv.Length, encryptedToken.Length);
        return System.Net.WebUtility.UrlEncode(Convert.ToBase64String(combinedContent));
     }" />
    
  7. Die ausgehende Richtlinie des Rückrufendpunkts wird aufgerufen, um zur Einzelseitenanwendung umzuleiten. Es legt das verschlüsselte Zugriffstoken in einem HttpOnly-Cookie fest, das auf SameSite festgelegt Strict und auf die Domäne des API-Verwaltungsgateways festgelegt ist. Da kein explizites Ablaufdatum festgelegt ist, wird das Cookie als Sitzungscookies erstellt und läuft ab, wenn der Browser geschlossen wird.

    <return-response>
        <set-status code="302" reason="Temporary Redirect" />
        <set-header name="Set-Cookie" exists-action="override">
            <value>@($"{{cookie-name}}={context.Variables.GetValueOrDefault<string>("cookie")}; Secure; SameSite=Strict; Path=/; Domain={{cookie-domain}}; HttpOnly")</value>
        </set-header>
        <set-header name="Location" exists-action="override">
            <value>{{return-uri}}</value>
        </set-header>
    </return-response>
    

API-Aufruffluss

Wenn die Einzelseitenanwendung über das Zugriffstoken verfügt, kann es das Token verwenden, um die downstream-API aufzurufen. Da das Cookie auf die Domäne der Einzelseitenanwendung festgelegt ist und mit dem SameSite=Strict-Attribut konfiguriert ist, wird es automatisch der Anforderung hinzugefügt. Das Zugriffstoken kann dann entschlüsselt werden, damit es zum Aufrufen der downstream-API verwendet werden kann. Das folgende Diagramm zeigt die Abfolge von Ereignissen für diesen Fluss.

Diagramm, das die API-Aufrufsequenz zeigt.

Der Fluss enthält die folgenden Schritte:

  1. Ein Benutzer wählt eine Schaltfläche in der Einzelseitenanwendung aus, um die downstream-API aufzurufen. Diese Aktion ruft eine JavaScript-Funktion auf, die den /graph/me-API-Endpunkt des API-Verwaltungsgateways aufruft.

  2. Da das Cookie auf die Domäne der Einzelseitenanwendung festgelegt ist und SameSite auf Strictfestgelegt ist, fügt der Browser das Cookie automatisch hinzu, wenn er die Anforderung an die API sendet.

  3. Wenn das API-Verwaltungsgateway die Anforderung empfängt, wird die eingehende Richtlinie des /graph/me Endpunkts aufgerufen. Die Richtlinie entschlüsselt das Zugriffstoken aus dem Cookie und speichert es in einer Variablen namens access_token:

    <set-variable name="access_token" value="@{
        try {
            string cookie = context.Request.Headers
                .GetValueOrDefault("Cookie")?
                .Split(';')
                .ToList()?
                .Where(p => p.Contains("{{cookie-name}}"))
                .FirstOrDefault()
                .Replace("{{cookie-name}}=", "");
            byte[] encryptedBytes = Convert.FromBase64String(System.Net.WebUtility.UrlDecode(cookie));
            byte[] iv = new byte[16];
            byte[] tokenBytes = new byte[encryptedBytes.Length - 16];
            Array.Copy(encryptedBytes, 0, iv, 0, 16);
            Array.Copy(encryptedBytes, 16, tokenBytes, 0, encryptedBytes.Length - 16);
            byte[] decryptedBytes = tokenBytes.Decrypt("Aes", Convert.FromBase64String("{{enc-key}}"), iv);
            char[] convertedBytesToChar = Encoding.UTF8.GetString(decryptedBytes).ToCharArray();
            return Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(convertedBytesToChar));
        } catch (Exception ex) {
            return null;
        }
    }" />
    
  4. Das Zugriffstoken wird der Anforderung der downstream-API als Authorization-Header hinzugefügt:

    <choose>
        <when condition="@(!string.IsNullOrEmpty(context.Variables.GetValueOrDefault<string>("access_token")))">
            <set-header name="Authorization" exists-action="override">
                <value>@($"Bearer {context.Variables.GetValueOrDefault<string>("access_token")}")</value>
            </set-header>
        </when>
    </choose>
    
  5. Die Anforderung wird mit dem Zugriffstoken, das dem Authorization-Header hinzugefügt wurde, an die downstreame API übergeben.

  6. Die Antwort der downstream-API wird direkt an die Einzelseitenanwendung zurückgegeben.

Bereitstellen dieses Szenarios

Vollständige Beispiele für die hier beschriebenen Richtlinien zusammen mit OpenAPI-Spezifikationen und einem vollständigen Bereitstellungshandbuch finden Sie in diesem GitHub-Repository.

Verbesserungen

Diese Lösung ist nicht produktionsbereit. Es soll veranschaulicht werden, was Sie tun können, indem Sie die hier beschriebenen Dienste verwenden. Berücksichtigen Sie die folgenden Faktoren, bevor Sie die Lösung in der Produktion verwenden.

  • In diesem Beispiel wird kein Ablauf des Zugriffstokens oder die Verwendung von Aktualisierungs- oder ID-Token implementiert.
  • Der Inhalt des Cookies in der Stichprobe wird über die AES-Verschlüsselung verschlüsselt. Der Schlüssel wird als geheimer Schlüssel im benannten Werten Bereich der API-Verwaltungsinstanz gespeichert. Um diesen benannten Wert besser zu schützen, können Sie einen Verweis auf einen geheimen Schlüssel verwenden, der in Azure Key Vault-gespeichert ist. Sie sollten Verschlüsselungsschlüssel regelmäßig im Rahmen Ihrer Schlüsselverwaltung Richtlinie drehen.
  • In diesem Beispiel werden nur Proxys für eine einzelne downstream-API aufgerufen, daher ist nur ein Zugriffstoken erforderlich. Dieses Szenario ermöglicht einen zustandslosen Ansatz. Aufgrund der Größenbeschränkung von HTTP-Cookies benötigen Sie jedoch einen zustandsbehafteten Ansatz, wenn Sie Aufrufe an mehrere downstream-APIs proxyn müssen. Anstatt ein einzelnes Zugriffstoken zu verwenden, umfasst dieser Ansatz das Speichern von Zugriffstoken in einem Cache und das Abrufen dieser Token basierend auf der aufgerufenen API und einem Schlüssel, der im Cookie bereitgestellt wird. Sie können diesen Ansatz mithilfe des API-Verwaltungs-Cache- oder eines externen Redis-Cachesimplementieren.
  • Da in diesem Beispiel das Abrufen von Daten nur über eine GET-Anforderung veranschaulicht wird, bietet es keinen Schutz vor CSRF- Angriffen. Wenn Sie andere HTTP-Methoden wie POST, PUT, PATCH oder DELETE verwenden, ist dieser Schutz erforderlich.

Beitragende

Dieser Artikel wird von Microsoft verwaltet. Sie wurde ursprünglich von den folgenden Mitwirkenden verfasst.

Hauptautor:

Anderer Mitwirkender:

Um nicht öffentliche LinkedIn-Profile anzuzeigen, melden Sie sich bei LinkedIn an.

Nächste Schritte