ASP.NET Core SignalR: Absicherung der Echtzeitkommunikation – Teil 2

Im ersten Teil der Artikelserie haben wir uns den generellen Verbindungsaufbau und Datenaustausch zwischen Client und Server mithilfe von SignalR angeschaut. Im zweiten Teil widmen wir uns nun der Absicherung unserer Echtzeitkommunikation. Ich zeige euch, wie ihr durch kleine Anpassungen im Code, einfach eine bestehende SignalR-Verbindung absichern könnt.

In diesem Artikel:

ASP.NET Core SignalR: Absicherung der Echtzeitkommunikation – Teil 2
Patrick Jahr ist Architekt bei Thinktecture. Sein Fokus liegt auf Backend-Systemen mit .NET Core und der Frontend-Entwicklung mit Angular.

Artikelserie

  1. Echtzeitkommunikation in Action
  2. Absicherung der Echtzeitkommunikation ⬅
  3. Mehr Sicherheit bei Tokens

Nun betrachten wir die Absicherung der SignalR-Verbindung zwischen dem Server und dem Client, unserer Demoanwendung, näher.

In unserer Beispielanwendung wird zur Authentifizierung ein IdentityServer genutzt, bei dem sich der Client authentifizieren kann. Um sich auch autorisieren zu können, können im IdentityServer Scope Policies erstellt und Nutzern zugewiesen. Dies ermöglicht, Nutzern spezifische Rechte zu vergeben und den Client durch den Berechtigungstoken zu autorisieren. Gemäß dem OIDC Authorization Code Flow erhält der Client vom IdentityServer einen Bearer Token. In diesem Artikel beschreibe ich, wie wir mithilfe eines Bearer Tokens die SignalR-Verbindung authentifizieren können. Wie das Token erstellt wird und wie der Authorization Code Flow implementiert wird, ist nicht bestandteil dieses Artikels. Genaure Informationen zur Implementierung des Flows finden sich in der genutzten Bibliothek angular-oauth2-oidc.

Den gesamten Source Code findet ihr in diesem GitHub Repository.

 

Server

Im Falle von klassischen Web-APIs werden Bearer Token genutzt, die im HTTP-Header mit gesendet werden. Dies ist jedoch bei einer Verbindung mit SignalR nicht so einfach. Das WebSocket-Protokoll unterstützt im Browser keine HTTP-Header und kann somit auch keinen Bearer Token mit senden. Um dies zu lösen, wird eine benutzerdefinierte Middleware in unserem Server hinzugefügt. Diese Middleware soll es ermöglichen, Aufrufe des Hubs zu authentifizieren. Hier zu passen wir die Startup.cs weiter an.

In der Methode AddJwtBearer werden alle Konfigurationen festgelegt, die für die Authentifizierung und Autorisierung der Server-API notwendig sind. Um den Token zu erhalten und dem Kontext des Request hinzuzufügen, wird das Event OnMessageReceived reagiert und geprüft, ob folgende zwei Dinge erfüllt sind:

  1. Handelt es sich um eine Anfrage an einen Hub?
  2. Besitzt der Request den notwendigen Query-Parameter access_token?

Sind diese Bedingungen erfüllt, wird der Token dem Request hinzugefügt.

				
					services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
    .AddJwtBearer("Bearer", options =>
    {
        options.Authority = "http://localhost:5000";
        options.RequireHttpsMetadata = false;
        options.Audience = "signalr-api";
        options.Events = new JwtBearerEvents
        {
            OnMessageReceived = context =>
            {
                var accessToken = context.Request.Query["access_token"];

                // If the request is for our hub...
                var path = context.HttpContext.Request.Path;
                if (!string.IsNullOrEmpty(accessToken) &&
                    (path.StartsWithSegments("/tictactoe")))
                {
                    // Read the token out of the query string
                    context.Token = accessToken;
                }

                return Task.CompletedTask;
            }
        };
    })
				
			

Um den Hub nicht mehr jedem Aufrufer zugänglich zu machen, wird das Attribut Authorize über der Klasse hinzugefügt. Dies muss nicht zwingend der Klasse hinzugefügt werden, sondern kann auch nur bei einzelnen Methoden erfolgen.

				
					[Authorize]
public class GamesHub : Hub
{
    // ...
}
				
			

Da auf der Seite des Servers alles vorbereitet ist, muss der Client angepasst werden. Denn, sollte der Client im aktuellen Zustand versuchen eine Verbindung mit dem Hub aufzubauen, bekommt dieser einen HTTP-Status 401 Not Authorized als Antwort. Dieser Status gibt Auskunft darüber, dass der Client nicht berechtigt ist, die Verbindung mit dem Hub aufzubauen.

Client

Durch die Client-Bibliothek @microsoft/signalr des Angular-Clients ist es sehr einfach, den Token als Query-Parameter mitzuschicken. Hier muss die Methode, die die HubConnection erstellt, erweitert werden. Es wird im withUrl-Aufruf ein neues Objekt erstellt, das eine accessTokenFactory als Eigenschaft besitzt. Die accessTokenFactory nimmt eine Funktion entgegen, deren Rückgabewert das eigentliche accessToken ist. So wird bei jedem Verbindungsaufbau (bspw. durch Abbruch) der aktuelle Token mitgeschickt, falls dieser sich durch einen Refresh-Mechanismus geändert hat. Baut der Client eine neue Verbindung zum Server auf, wird der Token als access_token-Query-Parameter dem HTTP-Request hinzugefügt.

Hinweis: Das Token wird nur beim initialen Aufbau der Verbindung überprüft und gilt dann für die gesamte Lebenszeit der Verbindung.

				
					const accessToken = this.oAuthService.getAccessToken();

if (!accessToken) {
    return;
}

this.hubConnection = new signalR.HubConnectionBuilder()
    .withUrl(`http://localhost:5000/tictactoe`, {
        accessTokenFactory: () => accessToken
    })
    .build();
				
			

Jetzt sind wir in der Lage, uns ordnungsgemäß zu authentifizieren und autorisieren, wenn das Authorize-Attribut im Hub oder einer Web API gesetzt ist.

Fazit

Wie auch schon bei der Implementierung von einer Echzeitkommunkation zwischen Server und Client, ist auch hier zu sehen, dass die Implementierung sehr einfach gestaltet ist. Wir konnten anhand der Beispielanwendung sehen, dass nur kleine Schritte zu tun sind, um unsere SignalR-Verbindung abzusichern. Jedoch ist die Implementierung des Code Flows nicht ganz zu unterschätzen. Ein großer Nachteil ist natürlich, dass über eine WebSocket-Verbindung keine HTTP-Header mitgesendet werden können, was es einfacher macht den Berechtigungstoken abzufangen. Daher ist es sinnvoll, nicht wie in unserem Beispiel, direkt alle Rechte zu vergeben, sondern nur den benötigten Teil der Rechte.

Im dritten Teil sehen wir, wie eine ASP.NET Core SignalR-Verbindung mit Hilfe eines Referenztoken abgesichert werden kann und wie Sie die Absicherung aktuell halten

Kostenloser
Newsletter

Aktuelle Artikel, Screencasts, Webinare und Interviews unserer Experten für Sie

Verpassen Sie keine Inhalte zu Angular, .NET Core, Blazor, Azure und Kubernetes und melden Sie sich zu unserem kostenlosen monatlichen Dev-Newsletter an.

Diese Artikel könnten Sie interessieren
Angular
Konfiguration von Lazy Loaded Angular Modulen

Konfiguration von Lazy Loaded Angular Modulen

Die Konfigurierbarkeit unserer Angular-Module ist für den Aufbau einer wiederverwendbaren Architektur unerlässlich. Aber in der jüngsten Vergangenheit hat uns Angular seine neue modullose Zukunft präsentiert. Wie sieht das Ganze jetzt aus? Wie konfigurieren wir jetzt unsere Lazy-Komponenten? Lasst uns gemeinsam einen Blick darauf werfen.
03.08.2023
ASP.NET Core
Architektur-Modernisierung: Migration von WCF zu gRPC mit ASP.NET Core – ein pragmatischer Ansatz

Architektur-Modernisierung: Migration von WCF zu gRPC mit ASP.NET Core – ein pragmatischer Ansatz

Viele Projekte mit verteilten Anwendungen in der .NET-Welt basieren noch auf der Windows Communication Foundation (WCF). Doch wie kommt man weg von der "Altlast" und wie stellt man seinen Code auf sowohl moderne als auch zukunftssichere Beine? Eine mögliche Lösung ist gRPC.

13.04.2023
ASP.NET Core
gRPC Code-First mit ASP.NET Core 7 und Blazor WebAssembly

gRPC Code-First mit ASP.NET Core 7 und Blazor WebAssembly

Wie in allen anderen browserbasierten Single-Page-Application (SPA) Frameworks, ist Blazor WebAssembly JSON-over-HTTP (über Web- oder REST-APIs) die bei weitem häufigste Methode, um Daten auszutauschen und serverseitige Vorgänge auszulösen. Der Client sendet eine HTTP-Anfrage mit JSON-Daten an eine URL, mitunter über unterschiedliche HTTP-Verben. Anschließend führt der Server eine Operation aus und antwortet mit einem HTTP-Statuscode und den resultierenden JSON-Daten. Warum sollte man das ändern? Nun, es gibt Gründe - vor allem wenn man in einem geschlossenen System ist und .NET sowohl im Frontend als auch im Backend einsetzt.
30.03.2023
ASP.NET Core
Blazor WebAssembly in .NET 7: UI-Performance-Optimierung auf Komponentenebene

Blazor WebAssembly in .NET 7: UI-Performance-Optimierung auf Komponentenebene

Stockende UI, keine Reaktion nach dem Klick auf einen Button oder einer Eingabe in einem Feld - dies sind nur wenige Beispiele alltäglicher Probleme, die der Nutzung von Client-Anwendungen im Allgemeinen, und bei Webanwendungen im Speziellen, immer wieder auftreten können. In diesem Artikel schauen wir uns an, wie wir komponentenbasierte UIs in Blazor WebAssembly optimieren können, um dadurch eine für die Benutzer zufriedenstellende Geschwindigkeit und ein flüssiges UI zu bekommen.
29.03.2023
.NET
Understanding and Controlling the Blazor WebAssembly Startup Process

Understanding and Controlling the Blazor WebAssembly Startup Process

There are a lot of things going on in the background, when a Blazor WebAssembly application is being started. In some cases you might want to take a bit more control over that process. One example might be the wish to display a loading screen for applications that take some time for initial preparation, or when users are on a slow internet connection. However, in order to control something, we need to understand what is happening first. This article takes you down the rabbit hole of how a Blazor WASM application starts up.
07.03.2023
.NET
Adding Superpowers to your Blazor WebAssembly App with Project Fugu APIs

Adding Superpowers to your Blazor WebAssembly App with Project Fugu APIs

Blazor WebAssembly is a powerful framework for building web applications that run on the client-side. With Project Fugu APIs, you can extend the capabilities of these apps to access new device features and provide an enhanced user experience. In this article, learn about the benefits of using Project Fugu APIs, the wrapper packages that are available for Blazor WebAssembly, and how to use them in your application.

Whether you're a seasoned Blazor developer or just getting started, this article will help you add superpowers to your Blazor WebAssembly app.
28.02.2023