Entra-ID-Authentifizierung in WinForms/WPF-Apps integrieren — Eine praxistaugliche Architektur mit MSAL.NET und dem WAM-Broker

· · Windows, C#, .NET, WinForms, WPF, Entra ID, Authentifizierung, Sicherheit, Technische Beratung

„Wir haben für jede interne Business-App einen eigenen Anmeldebildschirm gebaut und verwalten Passwörter in unserer eigenen Datenbank. Jedes Mal, wenn jemand das Unternehmen verlässt, müssen wir in jeder App einzeln das Konto deaktivieren.“ „Wir haben Microsoft 365 unternehmensweit eingeführt – können wir die Leute nicht einfach mit demselben Konto anmelden lassen?“ Das sind Themen, die in Beratungsgesprächen zur Modernisierung von Desktop-Apps in den letzten Jahren immer häufiger auftauchen. Manchmal kommt es auch anders formuliert – die IT-Abteilung hat dem Fachbereich gesagt, „hört auf, eigene Passwörter zu verwalten“, ausgelöst durch einen Passwort-Leck-Vorfall oder einen Vorstoß in Richtung Zero Trust.

Die kurze Antwort: Wenn Ihre Organisation Microsoft 365 verwendet, ist es eine sinnvolle Investition, die Anmeldung für Ihre internen WinForms/WPF-Apps auf Entra ID (früher Azure AD) umzustellen. Die App hält dann überhaupt keine Passwörter mehr, und Schutzmaßnahmen sowie Protokollierung auf Mandantenseite – Multi-Faktor-Authentifizierung, bedingter Zugriff, Anmeldeprotokolle – erstrecken sich automatisch auch auf Ihre internen Apps. Die Implementierung selbst passt in eine Bibliothek namens MSAL.NET und wenige Dutzend Codezeilen.

Allerdings gibt es eine Handvoll Fallstricke, die für Desktop-Apps spezifisch sind. Der altbewährte Ansatz, „Benutzername und Passwort in einem Textfeld entgegennehmen und im Hintergrund authentifizieren“ (ROPC), ist offiziell auf dem Weg zur Veraltung, und Sie dürfen ihn für neue Arbeiten nicht übernehmen. Persistieren Sie den Token-Cache nicht, sehen Benutzer bei jedem Start der App den Anmeldebildschirm. Und unter Windows macht es einen großen Unterschied für Benutzererlebnis wie Sicherheit, ob Sie den Broker (WAM) verwenden oder nicht. Dieser Artikel arbeitet alles durch, von einem minimalen konzeptionellen Überblick über die App-Registrierung und die MSAL.NET-Implementierung bis zu WAM, Caching, der Einführungsentscheidung und den Betriebsfallen.

1. Das Wichtigste zuerst

  • Geben Sie selbstverwaltete IDs und Passwörter zugunsten von Entra ID auf, werden Passwortspeicherung, Unterstützung beim Zurücksetzen, das Deaktivieren von Konten ausgeschiedener Mitarbeiter und die Anmeldeprotokollierung allesamt zur Aufgabe des Mandanten. Die drastische Verkleinerung der Verantwortungsfläche der App ist der größte Vorteil.
  • Eine Desktop-App ist ein öffentlicher Client (Public Client). Da die exe überall dort, wo sie verteilt wird, zurückentwickelt werden kann, kann (und darf) sie kein Client-Geheimnis halten. Auch die App-Registrierung muss als öffentlicher Client konfiguriert werden.1
  • ROPC (Resource Owner Password Credentials), bei dem die App selbst Benutzername und Passwort direkt entgegennimmt, wird offiziell als „veraltet“ dokumentiert, samt dazu veröffentlichtem Migrationsleitfaden. Es ist mit MFA und bedingtem Zugriff nicht kompatibel und wird faktisch unbrauchbar. Betrachten Sie die Übernahme für neue Arbeiten als verboten.23
  • Die Implementierung nutzt MSAL.NET (Microsoft.Identity.Client), und es gibt eigentlich nur ein grundlegendes Aufrufmuster: zunächst immer AcquireTokenSilent aufrufen und erst bei einer MsalUiRequiredException auf AcquireTokenInteractive zurückfallen.4
  • Unter Windows ist die Authentifizierung über den WAM-Broker (Web Account Manager) der empfohlene Ansatz. SSO mit dem Konto, mit dem der Benutzer bereits bei Windows angemeldet ist, Unterstützung für bedingten Zugriff, Windows Hello und FIDO-Schlüssel sowie geräteseitig gebundene Refresh-Token gibt es alle gratis mit einer einzigen WithBroker-Zeile.5
  • Vergessen Sie, den Token-Cache zu persistieren, erscheint der Anmeldebildschirm bei jedem Neustart der App erneut. Bauen Sie den verschlüsselten Cache aus Microsoft.Identity.Client.Extensions.Msal von Anfang an ein.6
  • Die Entra-Authentifizierung ist ein netzwerkabhängiger Mechanismus. Für Werkshallen-Apps, die vollständig offline laufen müssen, funktioniert sie nicht – verwenden Sie daher die Entscheidungstabelle in Kapitel 8, um die Machbarkeit vorab zu beurteilen.

2. Das Gesamtbild — Was es bedeutet, aufzuhören, Passwörter selbst zu verwalten

2.1 Was an selbstverwalteten Passwörtern problematisch ist

Verwaltet eine Business-App Passwörter in ihrer eigenen Benutzertabelle, fallen die folgenden Verantwortlichkeiten allesamt auf die App – das heißt auf uns, die Entwickler.

  • Speicherung: Wahl und Implementierung eines Hashing-Verfahrens (nicht selten trifft man noch auf 15 Jahre alte Tabellen mit ungesalzenem MD5)
  • Betrieb: Bearbeitung von Anfragen zum Zurücksetzen von Passwörtern, Sperrungen, Verteilung von Anfangspasswörtern
  • Lebenszyklus: Deaktivierung von Konten bei Austritt oder Versetzung. Bei fünf Apps sind das fünf getrennte Kontodeaktivierungen
  • Prüfung: Aufzeichnung und Aufbewahrung, wer wann angemeldet war. Multi-Faktor-Authentifizierung ist praktisch unmöglich umzusetzen

Delegiert man die Authentifizierung an Entra ID, verschwinden diese vier Verantwortlichkeiten aus dem Code der App und werden in der Verwaltung auf Mandantenebene gebündelt. Verlässt jemand das Unternehmen, blockiert die Deaktivierung des Entra-ID-Kontos sofort die Anmeldung an jeder App, und Anmeldeprotokolle werden automatisch geführt. Für Organisationen, die Microsoft 365 bereits eingeführt haben, bleibt kaum noch ein Grund, selbstverwaltete Authentifizierung weiter zu betreiben. (Für Organisationen mit Google Workspace, die den entsprechenden Mechanismus wollen – die Windows-Anmeldung selbst auf ein Google-Konto umzustellen – siehe „Was ist GCPW?“.)

2.2 Die Mindestkonzepte — Öffentliche Clients und Token

Ohne die lehrbuchmäßige Erklärung von OAuth 2.0 / OpenID Connect – hier nur die Konzepte, die für eine Desktop-App-Implementierung nötig sind.

Konzept Bedeutung für eine Desktop-App
Öffentlicher Client (Public Client) Eine App – eine exe, eine mobile App usw. –, die kein Geheimnis (Client Secret) sicher halten kann. Sie kann Token nur im Namen eines Benutzers erhalten
Vertraulicher Client (Confidential Client) Eine App wie ein Webserver oder ein Dienst, die ein Geheimnis oder Zertifikat halten kann. Eine Desktop-App ist das nicht
ID-Token Ein JWT, das darstellt, „wer diese Person ist“. Allein das genügt, wenn Sie nur eine Login-Funktion benötigen
Zugriffstoken (Access Token) Ein Passierschein zum Aufruf einer bestimmten API (Microsoft Graph oder Ihre eigene Web-API). Ziel (Audience) und Bereiche (Scopes) sind darin eingebacken
Refresh-Token Ein Token zur Erneuerung der beiden obigen Token ohne Interaktion. MSAL verwaltet es automatisch im Cache; die App bekommt es nie direkt zu Gesicht

Die erste Zeile ist die entscheidende. Da sich eine exe überall dort, wo sie verteilt wird, analysieren und dekompilieren lässt, hört ein darin eingebettetes „Geheimnis“ auf, geheim zu sein. Deshalb registriert sich die App als öffentlicher Client, der ohne Geheimnis läuft, und die eigentliche Authentifizierung (Passworteingabe, MFA) wird an einen Browser oder den Broker des Betriebssystems delegiert, wobei die App nur ein Token entgegennimmt. Dass die App niemals das Passwort des Benutzers berührt, ist das eigentliche Fundament dieses Designs.

2.3 ROPC ist eine Sackgasse — Was die Dokumentation tatsächlich sagt

Der altbewährte Instinkt lautet: „Bauen wir einfach unseren eigenen Anmeldebildschirm, der Benutzername und Passwort entgegennimmt, und lassen wir Entra ID das im Hintergrund prüfen.“ Das ist ROPC (Benutzername und Passwort direkt weitergeben), und obwohl es in MSAL.NET noch als AcquireTokenByUsernamePassword existiert, ist die aktuelle offizielle Dokumentation eindeutig.

  • ROPC für öffentliche Clients ist ausdrücklich als „aus Sicherheitsgründen veraltet“ dokumentiert, mit einem veröffentlichten Migrationsleitfaden hin zu sichereren Abläufen.3
  • ROPC ist mit MFA und bedingtem Zugriff nicht kompatibel. Ein Benutzer, für den der Mandant MFA erzwingt, wird über diesen Ablauf schlicht blockiert und kann sich nicht anmelden.2
  • SSO funktioniert nicht, persönliche Microsoft-Konten können nicht verwendet werden, und auch passwortlose Konten (FIDO, Authenticator) können sich nicht anmelden.2
  • Microsofts eigene Web-APIs bewegen sich zunehmend dahin, nur noch MFA-bestätigte Token zu akzeptieren, und die offizielle Dokumentation selbst stellt fest, dass „Apps, die auf ROPC setzen, ausgesperrt werden; Desktop-Apps sollten zur brokerbasierten Authentifizierung migrieren“.2

Da ein Mandant MFA jederzeit über seine eigenen Einstellungen erzwingen kann, ist die Übernahme von ROPC mit der Begründung „es funktioniert gerade“ ein Rezept dafür, eines Tages aufzuwachen und festzustellen, dass jeder einzelne Benutzer ausgesperrt ist. Selbst wenn eine bestehende App derzeit auf ROPC läuft, planen Sie die Migration ein. Praktisch gesehen gibt es nur zwei Abläufe zur Token-Beschaffung, die eine Desktop-App verwenden sollte.

Ablauf Wo er verwendet wird
Interaktiv (Broker / Browser) Gewöhnliche GUI-Apps. Die Hauptwahl
Gerätecodefluss (Device Code Flow) Umgebungen, in denen kein Browser angezeigt werden kann (eine Konsole über SSH usw.). Zeigt eine URL und einen Code an, und der Benutzer meldet sich über einen Browser auf einem separaten Gerät an

3. App-Registrierung — Die Konfiguration im Entra-Verwaltungscenter

Bevor Sie Code schreiben, registrieren Sie die App beim Mandanten. Kann ein Entwickler das nicht selbst tun, behandeln Sie diesen Abschnitt als fertiges Anfragedokument, das der IT-Abteilung übergeben werden kann.

3.1 Die eigentliche Registrierung

Erstellen Sie sie unter [App-Registrierungen] → [Neue Registrierung] im Microsoft-Entra-Verwaltungscenter (entra.microsoft.com).7

  • Name: Erscheint auf dem Zustimmungsbildschirm und in Anmeldeprotokollen, verwenden Sie also etwas, das die Fachseite erkennt, wie „Bestandsverwaltungssystem“.
  • Unterstützte Kontotypen: Für eine interne App ist „Nur Konten in diesem Organisationsverzeichnis“ (Single-Tenant) die einzig sinnvolle Wahl. Multi-Tenant ist nur für Produkte gedacht, die an mehrere Organisationen verteilt werden.
  • Notieren Sie sich die nach der Registrierung angezeigte Anwendungs-(Client-)ID und die Verzeichnis-(Mandanten-)ID und betten Sie sie in die Konfiguration der App ein (keine von beiden ist geheim).

3.2 Redirect-URIs — Wählen Sie die Plattform „Mobile und Desktopanwendungen“

Dies legt fest, wo die App nach der Authentifizierung das Token empfängt. Registrieren Sie unter [Authentifizierung] → [Plattform hinzufügen] → [Mobile und Desktopanwendungen] die zur verwendeten Authentifizierungsmethode passende URI.1

Authentifizierungsmethode Zu registrierende Redirect-URI
WAM-Broker (die Hauptwahl, Kapitel 5) ms-appx-web://microsoft.aad.brokerplugin/{Client-ID}
Systembrowser http://localhost
Eingebetteter Browser https://login.microsoftonline.com/common/oauth2/nativeclient

Den WAM-Wert ms-appx-web://... schreiben Sie nie in Ihren MSAL-Code, aber auf Seite der App-Registrierung ist er zwingend erforderlich.8 Angesichts des Browser-Fallbacks, der auftritt, wenn WAM nicht verfügbar ist (Kapitel 5), ist es praktisch, alle drei URIs aus der obigen Tabelle von Anfang an zu registrieren. Ein Punkt, der besondere Aufmerksamkeit verdient: Die Auflösung von WithDefaultRedirectUri() ist plattformabhängig – sie löst sich unter .NET Framework zu https://login.microsoftonline.com/common/oauth2/nativeclient auf, unter .NET (Core und später) zu http://localhost.9 Hat eine .NET-Framework-App nur ms-appx-web und http://localhost registriert und fällt WAM auf den Browser zurück, erhalten Sie durch die Nichtübereinstimmung mit nativeclient einen Authentifizierungsfehler. Registrieren Sie entweder alle drei, oder legen Sie sie mit WithRedirectUri(...) explizit fest. Ein weiterer klassischer Stolperstein, der zu einem Authentifizierungsfehler führt, ist die versehentliche Registrierung unter der Plattform „Web“ statt der richtigen.

3.3 API-Berechtigungen und Administratoreinwilligung

Fügen Sie unter [API-Berechtigungen] die delegierten Berechtigungen für die APIs hinzu, die die App aufrufen wird. Wenn Sie nur Anmeldung und die Anzeige des Profils benötigen, genügt das standardmäßig gewährte Microsoft-Graph-User.Read.

Ist das hinzugefügt, lassen Sie jemanden [Administratoreinwilligung für (Mandantenname) erteilen] ausführen.7 Das beseitigt den benutzerbezogenen Zustimmungsdialog bei der ersten Anmeldung. In Mandanten, in denen die Benutzereinwilligung deaktiviert ist, bleibt die erste Anmeldung ohne Administratoreinwilligung bei „Administratorgenehmigung erforderlich“ stehen, sodass Sie als Regel die Administratoreinwilligung vor der internen Verteilung einer App abschließen sollten.

3.4 Das Kennzeichen „Öffentliche Clientflows zulassen“

Der Schalter „Öffentliche Clientflows zulassen“ in den erweiterten Authentifizierungseinstellungen wird auf „Ja“ gesetzt, wenn Sie einen Ablauf verwenden, der nicht auf eine Redirect-URI angewiesen ist, wie den Gerätecodefluss oder die integrierte Windows-Authentifizierung.1 Für interaktiv (Browser/Broker) allein ist er nicht erforderlich. Beachten Sie außerdem, dass diese App-Registrierung niemals ein Client-Geheimnis oder ein Zertifikat erzeugt. Ein leerer Abschnitt „Zertifikate und Geheimnisse“ ist der korrekte Zustand für einen öffentlichen Client (das wird häufig genug verwechselt, dass Kapitel 9 darauf zurückkommt).

4. Die Implementierung mit MSAL.NET — Das Silent-dann-Interactive-Grundmuster

Fügen Sie Microsoft.Identity.Client über NuGet hinzu. Es gibt eigentlich nur ein Implementierungsmuster zu merken: immer zuerst AcquireTokenSilent aufrufen und nur bei Erhalt einer MsalUiRequiredException auf interaktiv zurückfallen. AcquireTokenInteractive ist so konzipiert, dass es niemals in den Cache schaut, sodass ein direkter Aufruf jedes Mal den Anmeldebildschirm anzeigt.4

using Microsoft.Identity.Client;

public sealed class AuthService
{
    private const string ClientId = "Anwendungs-(Client-)ID";
    private const string TenantId = "Verzeichnis-(Mandanten-)ID";
    private static readonly string[] Scopes = { "User.Read" };

    private readonly IPublicClientApplication _app;

    public AuthService()
    {
        _app = PublicClientApplicationBuilder.Create(ClientId)
            .WithAuthority(AzureCloudInstance.AzurePublic, TenantId)
            .WithRedirectUri("http://localhost")  // für den Systembrowser
            .Build();
        // In der Produktion hier die Persistierung des Token-Cache registrieren (Kapitel 6)
    }

    public async Task<AuthenticationResult> SignInAsync(IntPtr ownerHwnd)
    {
        // 1. Immer zuerst die stille Beschaffung anhand eines zwischengespeicherten Kontos versuchen
        var accounts = await _app.GetAccountsAsync();
        var account = accounts.FirstOrDefault();
        try
        {
            return await _app.AcquireTokenSilent(Scopes, account)
                             .ExecuteAsync();
        }
        catch (MsalUiRequiredException)
        {
            // 2. Den Anmeldebildschirm nur zeigen, wenn Interaktion erforderlich ist.
            // Der .NET-Framework-Standard ist die altmodische eingebettete WebView,
            // daher legen wir http://localhost-Redirect = Systembrowser explizit fest
            // (.NET 6+ verwendet ohnehin immer nur den Systembrowser)
            return await _app.AcquireTokenInteractive(Scopes)
                             .WithAccount(account)
                             .WithParentActivityOrWindow(ownerHwnd)
                             .WithUseEmbeddedWebView(false)
                             .ExecuteAsync();
        }
    }
}

Der Aufrufer übergibt das Handle des Besitzerfensters. Das verhindert, dass der Authentifizierungsdialog hinter dem eigenen Fenster der App verschwindet – ein Fehler, den man bei WAM zwingend vermeiden muss.5 Noch etwas: Lassen Sie WithUseEmbeddedWebView(false) in einer .NET-Framework-App nicht weg. Der Standard der interaktiven Authentifizierung von .NET Framework ist die eingebettete WebView, während der http://localhost-Redirect für den Systembrowser gedacht ist. (Stimmt die Kombination nicht, fallen Sie entweder auf den altmodischen eingebetteten Browser zurück – der bedingten Zugriff und Windows Hello/FIDO nicht unterstützt – oder Sie erhalten eine Nichtübereinstimmung der Redirect-URI.)10 Ab .NET 6 gibt es überhaupt keine eingebettete WebView mehr, sodass es immer der Systembrowser ist; dieser Aufruf ist dort überflüssig, aber unschädlich.

// WinForms (innerhalb einer Form-Methode)
var result = await _authService.SignInAsync(this.Handle);

// WPF
var hwnd = new System.Windows.Interop.WindowInteropHelper(this).Handle;
var result = await _authService.SignInAsync(hwnd);

this.Text = $"Angemeldet als: {result.Account.Username}";

Einige Punkte, die es wert sind, hervorgehoben zu werden.

  • Verwenden Sie für die gesamte App eine einzige IPublicClientApplication-Instanz. Da der Cache an die Instanz gebunden ist, macht das Erzeugen einer neuen Instanz über Create bei jedem Aufruf die stille Beschaffung zunichte.
  • MsalUiRequiredException ist kein „Fehler“ – es ist ein normaler, erwarteter Kontrollfluss, der bedeutet, dass Interaktion erforderlich ist. Er tritt beim ersten Start auf, wenn das Refresh-Token abgelaufen ist, oder wenn sich Anforderungen des bedingten Zugriffs ändern.
  • Die korrekte Verwendung besteht darin, AcquireTokenSilent unmittelbar vor jedem API-Aufruf aufzurufen. Gibt es ein gültiges Token im Cache, kehrt es sofort zurück, und es wird automatisch erneuert, sobald sich sein Ablauf nähert.4 Sie dürfen das Zugriffstoken nicht selbst festhalten und dessen Lebensdauer verwalten.
  • Warten Sie auf dem UI-Thread mit .Result oder .Wait(), kommt es zu einem Deadlock (siehe „Eine einseitige Spickzettel für async und den UI-Thread in WPF/WinForms“).

5. Der WAM-Broker — Die empfohlene Konfiguration unter Windows

Der Code in Kapitel 4 öffnet einen Browser, aber unter Windows gibt es eine bessere Option. WAM (Web Account Manager) ist ein in Windows 10 (1703+) und Windows Server 2019+ integrierter Authentifizierungsbroker, und die offizielle Dokumentation nennt vier Vorteile.5

  • Stärkere Sicherheit: Refresh-Token sind an das Gerät gebunden, sodass sie selbst bei Diebstahl nicht auf einem anderen Rechner verwendet werden können (Token-Schutz). Sicherheitsverbesserungen kommen weiterhin über OS-Updates.
  • Funktionsunterstützung: OS-/dienstintegrierte Authentifizierungsfunktionen wie Windows Hello, bedingter Zugriff und FIDO-Schlüssel funktionieren ohne zusätzlichen Code.
  • Systemintegration: Bei Windows bereits angemeldete Konten erscheinen im eingebauten Kontoauswähler, sodass die Anmeldung meist ganz ohne Passworteingabe abgeschlossen wird – de facto SSO.
  • Token-Schutz: Kann Richtlinien zum Token-Schutz im Rahmen des bedingten Zugriffs berücksichtigen.

Ist der Büro-PC Entra-verbunden (oder Hybrid-verbunden), sieht das Erlebnis so aus: App starten → Windows-Konto auswählen → sofort angemeldet, ohne irgendwo ein Passwort einzugeben.

5.1 Implementierung — WithBroker und das Paket

Die Verwendung von WAM erfordert MSAL.NET 4.52.0 oder höher und das zusätzliche Paket Microsoft.Identity.Client.Broker.5 Fügen Sie dem Builder aus Kapitel 4 WithBroker hinzu.

using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Broker;  // für WithBroker(BrokerOptions)

var brokerOptions = new BrokerOptions(BrokerOptions.OperatingSystems.Windows)
{
    Title = "Bestandsverwaltungssystem"  // im Kontoauswähler angezeigter Titel
};

_app = PublicClientApplicationBuilder.Create(ClientId)
    .WithAuthority(AzureCloudInstance.AzurePublic, TenantId)
    .WithDefaultRedirectUri()
    .WithParentActivityOrWindow(() => _ownerHwnd)  // bei WAM zwingend erforderlich
    .WithBroker(brokerOptions)
    .Build();

Sie können die stille Beschaffung um eine weitere Zeile verstärken. Ist kein Konto im Cache vorhanden, lässt sich mit PublicClientApplication.OperatingSystemAccount eine stille Anmeldung mit „dem gerade bei Windows angemeldeten Konto“ versuchen. Das ist das offiziell empfohlene Muster, um eine dialogfreie Anmeldung bereits ab dem allerersten Start zu erreichen.8

var accounts = await _app.GetAccountsAsync();
var account = accounts.FirstOrDefault()
              ?? PublicClientApplication.OperatingSystemAccount;
try
{
    return await _app.AcquireTokenSilent(Scopes, account).ExecuteAsync();
}
catch (MsalUiRequiredException)
{
    return await _app.AcquireTokenInteractive(Scopes).ExecuteAsync();
}

Registrieren Sie dazu, wie in Abschnitt 3.2 behandelt, auf Seite der App-Registrierung ms-appx-web://microsoft.aad.brokerplugin/{Client-ID} unter der Plattform „Mobile und Desktopanwendungen“.5 Vergessen Sie das, schlägt die interaktive Authentifizierung mit einem Broker-Fehler fehl. Noch ein Punkt: Das WithDefaultRedirectUri() im obigen Beispiel legt nur die Redirect-URI für den Fall fest, dass WAM nicht verfügbar ist und auf den Browser zurückgefallen wird, und dessen Auflösung ist plattformabhängig (.NET Framework → nativeclient, .NET → http://localhost).9 Beides funktioniert, wenn Sie alle drei URIs aus der Tabelle in Abschnitt 3.2 registriert haben; möchten Sie die Registrierung enger halten, legen Sie sie mit WithRedirectUri(...) explizit fest.

5.2 Die Einschränkungen von WAM — Dinge, die Sie ins Stolpern bringen, wenn Sie sie nicht kennen

Einschränkung Details
Betriebssystem Windows 10 (1703)+ / Windows Server 2019+. Auf früheren Versionen, Mac oder Linux fällt es automatisch auf den Browser zurück5
Identitätsanbieter Nur Entra ID. Azure AD B2C und AD-FS-Authorities werden nicht unterstützt (fällt auf den Browser zurück)5
Ausführungskontext Setzt voraus, dass UI innerhalb einer interaktiven Benutzersitzung angezeigt werden kann. Windows-Dienste, Aufgabenplanung (außerhalb einer Benutzersitzung) oder die Ausführung als anderer Benutzer über runas führen konstruktionsbedingt zu einem Fehler5

Die dritte Zeile ist besonders wichtig. „Es funktioniert einwandfrei in der GUI-App, aber derselbe Code schlägt fehl, wenn er in einem nächtlichen Batch-Job wiederverwendet wird“ ist erwartetes Verhalten, kein Fehler. Unbeaufsichtigte Ausführung ist ein Bereich, in dem Sie das Design in Richtung Anwendungsberechtigungen (ein vertraulicher Client) aufteilen müssen, statt ein delegiertes Benutzertoken wiederzuverwenden. Da der Fallback als gestaltetes Merkmal eingebaut ist, ist „zuerst WAM versuchen, bei Fehlschlag auf den Browser zurückfallen“ etwas, das MSAL Ihnen in einer einzigen Codezeile liefert.

6. Den Token-Cache persistieren — Nicht bei jedem Neustart den Anmeldebildschirm zeigen

Der Token-Cache von MSAL.NET liegt standardmäßig nur im Arbeitsspeicher, und ihn zu persistieren ist auf dem Desktop die eigene Verantwortung der App. Ohne Persistierung schlägt AcquireTokenSilent bei jedem Prozessneustart fehl und fällt auf die interaktive Anmeldung zurück.4 „Beim Pilotversuch lief es einwandfrei, aber Benutzer haben sich beschwert, dass der Anmeldebildschirm jeden einzelnen Morgen erschien“ – das ist fast immer die Ursache.

Die offizielle Empfehlung ist, die plattformübergreifende Cache-Bibliothek Microsoft.Identity.Client.Extensions.Msal (NuGet) zu verwenden.6

using Microsoft.Identity.Client.Extensions.Msal;

var storageProperties = new StorageCreationPropertiesBuilder(
        "msal_cache.dat",
        Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
            "KomuraSoft", "InventoryApp"))
    .Build();

var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties);
cacheHelper.RegisterCache(_app.UserTokenCache);  // einmalig, direkt nach Build() registrieren

Unter Windows wird der Cache verschlüsselt gespeichert. Das in der offiziellen Dokumentation gezeigte Beispiel einer handgestrickten Implementierung verschlüsselt das Token mit ProtectedData (DPAPI, DataProtectionScope.CurrentUser), bevor es in einer Datei gespeichert wird – Extensions.Msal ist als eine auf derselben Idee aufbauende, produktionsreife Bibliothek positioniert.6 Das Prinzip, dass „benutzerbezogene Geheimnisse mit benutzerbezogenem DPAPI geschützt werden sollten“, ist dasselbe, das für Konfigurationsdateien in „Vertrauliche Daten in Windows-Apps speichern — Klartext-Einstellungen mit DPAPI vermeiden“ behandelt wird. Behandeln Sie eine selbstgebaute Implementierung, die den Token-Cache als Klartext-JSON speichert, mit derselben Schwere wie die Speicherung eines Connection-Strings im Klartext.

Drei Betriebshinweise.

  • Die Cache-Persistierung ist auch bei Verwendung von WAM erforderlich. MSAL speichert weiterhin das ID-Token und die Kontometadaten in seinem eigenen Cache.8
  • Der Speicherort ist in der Regel %LOCALAPPDATA%\Firmenname\AppName. Wegen der Bindung von DPAPI funktioniert die Entschlüsselung auf einem anderen PC oder unter einem anderen Benutzer nicht, aber die einzige Folge ist, dass die stille Beschaffung fehlschlägt und auf eine erneute Anmeldung zurückfällt – ein echter Schaden entsteht nicht.
  • Implementieren Sie „Abmelden“, indem Sie mit GetAccountsAsync aufgelistete Konten mit RemoveAsync entfernen; das Löschen der Cache-Datei ist nicht nötig. Allerdings löscht RemoveAsync nur den lokalen Cache von MSAL – WAM, Browser oder die Windows-Anmeldesitzung bleiben alle intakt. Da die nächste interaktive Anmeldung dasselbe Konto still erneut authentifizieren kann, sollten Sie, wenn Sie auf einem gemeinsam genutzten PC einen echten Kontowechsel benötigen, dies explizit gestalten: entweder WithPrompt(Prompt.SelectAccount) an AcquireTokenInteractive anhängen, damit der Kontoauswähler immer angezeigt wird, oder je nach Anforderung auch den Abmelde-Endpunkt des Mandanten verwenden. Unterscheiden Sie klar zwischen „den lokalen Cache leeren“ und „einer echten Abmeldung“.

7. Was mit dem beschafften Token zu tun ist — Drei Konfigurationen

Ist die Authentifizierung erfolgreich, teilt sich die weitere Verwendung in drei Muster. Wie weit Sie gehen, bestimmt, welche zusätzliche Konfiguration Sie brauchen.

Konfiguration Verwendetes Token Was sonst noch nötig ist
(1) Nur Anmeldung ID-Token (AuthenticationResult.Account / ClaimsPrincipal) Nichts (User.Read allein genügt)
(2) Aufruf von Microsoft Graph Zugriffstoken für Graph Zu den aufzurufenden APIs passende Graph-Berechtigungen und -Einwilligung
(3) Schutz Ihrer eigenen Web-API Zugriffstoken für Ihre eigene API App-Registrierung und Scope-Freigabe auf API-Seite, plus Token-Validierung auf API-Seite

7.1 Konfiguration nur für Anmeldung — So klein wie möglich beginnen

Möchten Sie nur „unsere eigene Passwortprüfung ersetzen, ohne irgendeine Cloud-API aufzurufen“, genügt es, die Kontoinformationen aus dem Anmeldeergebnis mit der eigenen Berechtigungstabelle der App abzugleichen. Ersetzen Sie den Schlüssel Ihrer users-Tabelle durch die Entra-Objekt-ID (die auch bei einer Änderung des UPN, etwa nach einer Namensänderung, stabil bleibt) und entfernen Sie die Passwortspalte. Da Sie das lokale DB-Design nicht ändern müssen und nur die Authentifizierung austauschen, ist dies der am einfachsten zu empfehlende erste Schritt.

7.2 Microsoft Graph aufrufen

Werfen Sie einfach das User.Read-Zugriffstoken direkt an Microsoft Graph, und Sie können das Profil oder Foto des angemeldeten Benutzers abrufen.

var http = new HttpClient();
http.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", result.AccessToken);
var me = await http.GetStringAsync("https://graph.microsoft.com/v1.0/me");

Erweitern Sie es auf Kalender, E-Mail-Versand, Teams-Benachrichtigungen und Ähnliches, fügen Sie die entsprechende Berechtigung (Mail.Send usw.) hinzu und holen Sie die Administratoreinwilligung erneut ein. Benachrichtigungs-E-Mails aus internen Apps auf Graph zu konsolidieren, passt auch gut zu dem in „Massen-E-Mail-Versand für KMU ohne Bindung an einen bestimmten Anbieter gestalten“ behandelten Ansatz.

7.3 Ihre eigene Web-API schützen — Bis zur Validierung von Audience und Scopes

Ruft Ihre Desktop-App die Web-API Ihres eigenen Unternehmens auf, erstellen Sie auch für die API-Seite eine separate App-Registrierung, geben einen Scope wie api://{Client-ID der API}/access_as_user frei, und die Desktop-Seite fordert ein Token mit diesem Scope an. Wichtig auf API-Seite ist, dass [Authorize] allein offiziell als nicht ausreichend dokumentiert ist.11 Drei Dinge müssen validiert werden.

  1. Signatur und Aussteller: Ist dies ein JWT, das von der Entra ID des korrekten Mandanten ausgestellt wurde (bei ASP.NET Core + Microsoft.Identity.Web übernimmt das die Middleware)
  2. Audience (aud): Ist das Ziel des Tokens diese API selbst? Lassen Sie nicht zu, dass ein für Graph ausgestelltes Token gegen Ihre eigene API wiederverwendet wird
  3. Scope (scp-Anspruch): Enthält es den erwarteten Scope? Mit Microsoft.Identity.Web lässt sich das mit dem Attribut [RequiredScope("access_as_user")] deklarieren11

Überspringen Sie die Schritte 2 und 3, erhalten Sie „eine API, die jeden durchlässt, solange das Token so aussieht, als käme es von Entra“. Beziehen Sie das neben den Punkten zu Kommunikation und Eingabevalidierung aus „Die Mindest-Sicherheitscheckliste für die Windows-App-Entwicklung“ als Ziel der Designprüfung ein.

8. Die Einführungsentscheidung — Sollte ein vollständig internes Tool Entra-Authentifizierung erhalten?

Nicht jede interne App sollte das erhalten. Hier eine Entscheidungstabelle.

Situation Empfehlung Begründung
Microsoft 365 / Entra ID ist unternehmensweit eingeführt, und die App hat bereits ein Anmeldekonzept Einführen Die gesamte Haftung selbstverwalteter Passwörter verschwindet. Implementierungsaufwand ist gering
Die App ruft Ihre eigene Web-API oder Cloud-Ressourcen auf Einführen Ein Authentifizierungsfundament ist zum Schutz der API unverzichtbar. Zuverlässiger als ein eigenes Token-Schema zu erfinden
Es besteht eine Prüfungsanforderung (Aufzeichnung, wer wann verwendet hat, verpflichtende MFA) Einführen Anmeldeprotokolle und bedingter Zugriff werden auf Mandantenseite konsolidiert
Ein Einzweck-Tool ohne Anmeldekonzept (ein Konverter, ein Viewer usw.) Nicht nötig Es gibt keinen Grund, Authentifizierung hinzuzufügen. Die Windows-Anmeldung genügt bereits
Läuft in einer vollständig Offline-Umgebung (eine abgeschottete Fertigungslinie, ein Außendienst-Laptop) Nicht machbar oder erfordert sorgfältiges Design Sowohl die erste Anmeldung als auch die Token-Erneuerung erfordern das Netzwerk
Entra ID ist nicht eingeführt (nur On-Prem-AD oder nur Google Workspace) Alternativen prüfen Für Ersteres AD-Authentifizierung (integrierte Windows-Authentifizierung), für Letzteres ist ein Mechanismus auf Google-Seite die natürliche Wahl

Achten Sie besonders auf die Offline-Anforderung. AcquireTokenSilent kann offline ein Token zurückgeben, solange das zwischengespeicherte Zugriffstoken noch gültig ist (Faustregel: etwas über eine Stunde), aber sobald es abläuft, erfordert die Erneuerung das Netzwerk. Vor der Einführung müssen Sie diese Annahme zur Token-Lebensdauer mit dem tatsächlichen Arbeitsablauf vor Ort abgleichen.

9. Betriebsfallen — Anfragen, die nach der Einführung auftauchen

Die Einführung ist nicht das Ende der Geschichte – es gibt wiederkehrende Anfragen, die in der Betriebsphase auftauchen. Hier sind sie, vorab zusammengestellt.

  • „Gestern ging es noch, plötzlich kann ich mich nicht mehr anmelden“: Der Hauptverdächtige ist eine Änderung der Richtlinie für bedingten Zugriff. Aktiviert die IT-Abteilung etwa „unregistrierte Geräte blockieren“, beginnt die Anmeldung zu scheitern, ohne dass sich an der App selbst irgendetwas geändert hätte. Der schnellste Weg zur Eingrenzung ist, im Entra-Verwaltungscenter in den Anmeldeprotokollen den Fehlergrund für den betroffenen Benutzer zu prüfen. Eine WAM-Konfiguration macht die App widerstandsfähiger gegen solche Richtlinienanforderungen, was diese Art von Reibung von sich aus reduziert.5
  • „Ich habe eine Benachrichtigung erhalten, dass ein Geheimnis bald abläuft – ist diese App in Ordnung?“: Öffentliche Clients haben von vornherein nie ein Geheimnis oder Zertifikat, es gibt also nichts, was ablaufen könnte. Kommt diese Frage auf, liegt entweder eine Verwechslung mit der App-Registrierung eines vertraulichen Clients vor, oder jemand hat auf einer Registrierung für öffentliche Clients unnötigerweise ein Geheimnis erstellt (in diesem Fall kann es gelöscht werden). Dass ein Ausfall durch ablaufendes Geheimnis strukturell nicht auftreten kann, ist ein versteckter Vorteil dieser Konfiguration.
  • „Beim ersten Start heißt es ‚Administratorgenehmigung erforderlich‘“: Das ist eine fehlende Administratoreinwilligung aus Abschnitt 3.3. Fügen Sie später eine neue Berechtigung hinzu, erscheint dieselbe Meldung erneut, bis Sie für die Ergänzung erneut die Einwilligung einholen.
  • „Ich habe es in einen nächtlichen Batch-Job eingebunden, und es funktioniert nicht“: Wie in Abschnitt 5.2 behandelt, setzt WAM eine interaktive Sitzung voraus. Gestalten Sie unbeaufsichtigte Verarbeitung um Anwendungsberechtigungen herum, statt ein delegiertes Benutzertoken wiederzuverwenden.
  • Verteilung und Updates: MSAL wird aktiv gepatcht, sodass Sie einen Mechanismus benötigen, der Bibliotheks-Updates an jeden Endpunkt bringt. Betrachten Sie das zusammen mit der Verifizierung des Update-Pfads, die in „Sicherheitsdesign für automatische Updates“ behandelt wird.

10. Zusammenfassung

Die Unterstützung der Entra-ID-Authentifizierung in einer WinForms/WPF-App läuft auf diese sechs Punkte hinaus.

  • Der eigentliche Punkt ist, aufzuhören, Passwörter selbst zu verwalten. Speicherung, Zurücksetzen, Umgang mit ausgeschiedenen Mitarbeitern und Prüfung werden allesamt auf Mandantenseite konsolidiert
  • Eine Desktop-App ist ein öffentlicher Client. Sie kann kein Geheimnis halten, und sie braucht auch keines
  • ROPC steht vor der Veraltung. Bauen Sie keinen neuen Bildschirm, der Benutzername und Passwort direkt entgegennimmt
  • Die Implementierung folgt eindeutig dem Muster AcquireTokenSilentAcquireTokenInteractive von MSAL.NET
  • Unter Windows verschafft Ihnen der WAM-Broker (WithBroker) SSO, bedingten Zugriff und Windows-Hello-Unterstützung
  • Bauen Sie die Persistierung des Token-Cache (Extensions.Msal / DPAPI-Schutz) von Anfang an ein

Mit der minimalen Konfiguration „nur den Login austauschen“ (Abschnitt 7.1) lässt sich die Auswirkung auf eine bestehende App auf den Anmeldebildschirm und die Benutzertabelle beschränken, und die Änderung passt oft in wenige Tage Arbeit. Kommen andererseits bedingter Zugriff oder Offline-Anforderungen ins Spiel, brauchen Sie Designentscheidungen, die sowohl auf der Konfiguration des Mandanten als auch auf der tatsächlichen betrieblichen Praxis fußen. Sind Sie sich unsicher, wie weit Sie die Konfiguration Ihrer App treiben sollten oder wie Sie die Migration weg von selbstverwalteter Authentifizierung staffeln, helfen wir gerne.

Verwandte Artikel

Verwandte Beratungsleistungen

Die Komura Software LLC übernimmt die Integration der Entra-ID-Authentifizierung in bestehende WinForms/WPF-Apps (Design der App-Registrierung, MSAL.NET-Implementierung, Migrationsplanung von selbstverwalteter Authentifizierung), Designprüfungen für die Token-Validierung in Ihren eigenen Web-APIs sowie die Eingrenzung von Anmeldefehlern im Zusammenhang mit bedingtem Zugriff.

  1. Microsoft Learn, Desktop app that calls web APIs: Code configuration. Zu Redirect-URIs für Desktop-Apps (die Plattform für Mobile und Desktop, nativeclient / localhost) und zur Bedeutung der Einstellung „Öffentliche Clientflows zulassen“.  2 3

  2. Microsoft Learn, Microsoft identity platform and OAuth 2.0 Resource Owner Password Credentials. Dazu, warum ROPC nicht verwendet werden sollte, seine Inkompatibilität mit MFA und die daraus resultierende Blockierung, den Trend zur Aussperrung ROPC-abhängiger Apps sowie die Empfehlung, dass Desktop-Apps zur brokerbasierten Authentifizierung migrieren sollten.  2 3 4

  3. Microsoft Learn, Desktop app that calls web APIs: Acquire a token using username and password. Dazu, dass der Benutzername-Passwort-Ablauf (ROPC) aus Sicherheitsgründen als veraltet gilt, zur Anleitung hin zur Migration sowie zur fehlenden Unterstützung für MFA, bedingten Zugriff und SSO.  2

  4. Microsoft Learn, Get a token from the token cache using MSAL.NET. Zum empfohlenen Muster, zuerst AcquireTokenSilent aufzurufen und bei MsalUiRequiredException auf interaktiv zurückzufallen, zur automatischen Erneuerung über Cache und Refresh-Token sowie zum Leeren des Cache durch Entfernen eines Kontos.  2 3 4

  5. Microsoft Learn, Using MSAL.NET with Web Account Manager (WAM). Zu den Vorteilen des Brokers (stärkere Sicherheit, Unterstützung für Windows Hello / bedingten Zugriff / FIDO, den Kontoauswähler, Token-Schutz), zu den Anforderungen MSAL.NET 4.52.0+ und dem Paket Microsoft.Identity.Client.Broker, zum zwingenden WithBroker und Handle des übergeordneten Fensters, zur ms-appx-web-Redirect-URI, zu unterstützten Betriebssystemversionen und Fallback-Verhalten sowie zur Anforderung einer interaktiven Sitzung.  2 3 4 5 6 7 8 9

  6. Microsoft Learn, Token cache serialization. Zur Empfehlung, dass Desktop-Apps den plattformübergreifenden Cache von Microsoft.Identity.Client.Extensions.Msal verwenden sollten, zur Verwendung von MsalCacheHelper sowie zum Beispiel einer handgestrickten Serialisierung mit ProtectedData (DPAPI, CurrentUser-Bereich).  2 3

  7. Microsoft Learn, Register an application with the Microsoft identity platform. Zum Ablauf der App-Registrierung im Entra-Verwaltungscenter, zur Wahl der unterstützten Kontotypen, zum Erhalt der Client-ID und zur Administratoreinwilligung.  2

  8. Microsoft Learn, Desktop app that calls web APIs: Acquire a token by using WAM. Zur Notwendigkeit der Cache-Persistierung auch bei Verwendung von WAM, zum empfohlenen Muster der stillen Anmeldung mittels OperatingSystemAccount sowie zur Konfiguration der Redirect-URI auf Seite der App-Registrierung.  2 3

  9. Microsoft Learn, Default reply URI. Dazu, dass die von WithDefaultRedirectUri gesetzte Redirect-URI plattformabhängig ist (https://login.microsoftonline.com/common/oauth2/nativeclient für .NET-Framework-Desktop, http://localhost für .NET Core).  2

  10. Microsoft Learn, Using web browsers (MSAL.NET). Zur Browser-Unterstützungstabelle je Framework (.NET Framework 4.6.2+ standardmäßig eingebettet, .NET 6+ nur Systembrowser), zur Notwendigkeit einer http://localhost-Redirect-URI für den Systembrowser sowie zum Umschalten über WithUseEmbeddedWebView. 

  11. Microsoft Learn, Protected web API: Verify scopes and app roles. Dazu, warum das Attribut [Authorize] allein nicht ausreicht, zur Notwendigkeit, den scp-Anspruch (Scope) zu validieren, sowie zur deklarativen Validierung über das RequiredScope-Attribut von Microsoft.Identity.Web.  2

Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.

Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.

Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.

Häufige Fragen

Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.

Warum sollte eine WinForms- oder WPF-App Entra ID statt eines eigenen Anmeldebildschirms verwenden?
Delegiert man die Authentifizierung an Entra ID, entfallen für die App die Speicherung von Passwörtern, die Unterstützung beim Zurücksetzen, das Deaktivieren von Konten ausgeschiedener Mitarbeiter und die Anmeldeprotokollierung – all das wandert in die Verwaltung auf Mandantenebene. Verlässt jemand das Unternehmen, blockiert die Deaktivierung des Entra-ID-Kontos sofort den Zugriff auf jede verbundene App. Schutzmaßnahmen auf Mandantenseite wie Multi-Faktor-Authentifizierung, bedingter Zugriff und Anmeldeprotokolle greifen automatisch auch für Ihre internen Apps. Die Implementierung passt in die MSAL.NET-Bibliothek und wenige Dutzend Codezeilen.
Kann ich ein eigenes Anmeldeformular behalten, das Benutzername und Passwort entgegennimmt (ROPC)?
Nein – Sie sollten ROPC nicht für neue Arbeiten übernehmen. Microsoft dokumentiert den Ablauf offiziell als aus Sicherheitsgründen veraltet, und er ist mit MFA und bedingtem Zugriff nicht kompatibel, sodass Benutzer unter einer MFA-Richtlinie schlicht von der Anmeldung ausgeschlossen werden. Außerdem bricht er SSO und kann passwortlose Konten wie FIDO-Schlüssel nicht handhaben. Eine Desktop-App sollte stattdessen interaktive Authentifizierung über einen Broker oder Browser verwenden und nur dort auf den Gerätecodefluss zurückgreifen, wo kein Browser angezeigt werden kann.
Was ist der WAM-Broker und warum wird er unter Windows empfohlen?
WAM (Web Account Manager) ist ein in Windows 10 (1703 und höher) und Windows Server 2019 und höher integrierter Authentifizierungsbroker. Ihn mit einer einzigen WithBroker-Zeile zu aktivieren, liefert SSO mit dem bereits bei Windows angemeldeten Konto, Unterstützung für Windows Hello, bedingten Zugriff und FIDO-Schlüssel sowie geräteseitig gebundene Refresh-Token, die auf einem anderen Rechner nicht wiederverwendet werden können. Er erfordert MSAL.NET 4.52.0 oder höher, das Paket Microsoft.Identity.Client.Broker sowie die Registrierung der ms-appx-web-Broker-Redirect-URI in der App-Registrierung. Auf nicht unterstützten Systemen fällt er automatisch auf den Browser zurück.
Warum erscheint der Anmeldebildschirm bei jedem Neustart meiner App erneut?
Der Token-Cache von MSAL.NET liegt standardmäßig nur im Arbeitsspeicher, sodass die App ihn auf dem Desktop selbst persistieren muss. Ohne Persistierung schlägt AcquireTokenSilent bei jedem Prozessneustart fehl und fällt auf die interaktive Anmeldung zurück. Die offizielle Empfehlung ist die Bibliothek Microsoft.Identity.Client.Extensions.Msal, die den Cache unter Windows mit benutzerbezogenem DPAPI verschlüsselt speichert. Beachten Sie, dass die Persistierung des Cache auch bei Verwendung des WAM-Brokers weiterhin nötig ist, da MSAL das ID-Token und die Kontometadaten in seinem eigenen Cache hält.

Autorenprofil

Profilseite des Artikelautors.

Go Komura

Geschäftsführer von KomuraSoft LLC

Spezialisiert auf Windows-Softwareentwicklung, technische Beratung und Fehleranalyse, insbesondere bei bestehenden Systemen und schwer reproduzierbaren Störungen.

Zurück zum Blog