API-Keys sicher konfigurieren in .NET-Anwendungen (Teil 3)
API-Keys sind wie digitale Schlüssel. Sie geben einer Anwendung Zugriff auf externe Dienste — im Fall von EchoPlay auf die Spotify-Web-API oder die Apple-Music-API. Wer diesen Schlüssel besitzt, kann im Namen der Anwendung Anfragen stellen, also beispielsweise Daten abrufen oder, je nach API, Aktionen auslösen. Warum diese Schlüssel niemals im Code stehen sollten und wie EchoPlay das Problem löst, zeigt dieser Artikel.
Warum API-Keys nicht in den Code gehören
Wenn du einen solchen API-Key direkt in den Code schreibst, ist das ein ernstes Problem. Kompilierter Code kann mit frei verfügbaren Tools wieder in lesbaren Code zurückverwandelt werden. Außerdem landet der Quellcode häufig in einem Versionskontrollsystem wie Git — und was einmal in Git ist, bleibt dort, auch wenn man es später löscht. Sicherheits-Scanner durchsuchen GitHub und ähnliche Dienste automatisch nach API-Keys. Gefundene Schlüssel werden innerhalb von Minuten missbraucht.
Das ist nicht nur ein Sicherheitsproblem, sondern auch praktisch unpraktisch: Wenn sich ein Schlüssel ändert, muss man sonst den Code anpassen, neu kompilieren und die App erneut veröffentlichen. Mit dem richtigen Ansatz ändert man stattdessen nur eine Konfigurationsdatei.
Wie löst EchoPlay das Problem?
EchoPlay trennt Konfiguration von Code. Die Schlüssel stehen in einer Datei namens appsettings.json, die beim Start der Anwendung geladen wird. Diese Datei ist nicht im öffentlichen Git-Repository enthalten.
Die Struktur in appsettings.json sieht so aus:
{
"Spotify": {
"ClientId": "",
"ClientSecret": "",
"AuthBaseUrl": "https://accounts.spotify.com/",
"ApiBaseUrl": "https://api.spotify.com/"
}
}
Die Felder ClientId und ClientSecret sind bewusst leer — sie werden beim Deployment gefüllt, nicht im Code. Für die Entwicklung gibt es außerdem eine appsettings.Development.json, die nur lokal existiert und nie in Git eingecheckt wird. In der .gitignore-Datei ist sie explizit ausgeschlossen.
Wie liest EchoPlay die Konfiguration ein?
In App.xaml.cs wird beim Start ein sogenannter Host aufgebaut. Der Host ist das Herz der Anwendung — er lädt die Konfiguration, richtet die Abhängigkeiten ein und startet die Anwendung. Die Konfigurationsdateien werden dabei in einer bestimmten Reihenfolge geladen:
builder.Configuration
.AddJsonFile("appsettings.json", optional: false)
.AddJsonFile("appsettings.Development.json", optional: true);
Die erste Datei ist Pflicht (optional: false) — sie enthält die Grundstruktur. Die zweite ist optional und überschreibt nur, was sie selbst definiert. So können Entwickler lokal andere Werte nutzen, ohne die Produktionskonfiguration zu verändern.
Die Spotify-Konfiguration wird dann als typsicheres Objekt gebunden:
SpotifyOptions spotifyOptions = builder.Configuration
.GetSection("Spotify")
.Get<SpotifyOptions>()
?? throw new InvalidOperationException("Spotify-Konfiguration fehlt.");
builder.Services.AddSingleton(spotifyOptions);
SpotifyOptions ist eine einfache C#-Klasse mit Properties wie ClientId, ClientSecret, AuthBaseUrl und ApiBaseUrl. Der ??-Operator bedeutet: wenn Get<SpotifyOptions>() null zurückgibt, wird sofort eine Ausnahme geworfen — die App startet gar nicht erst, wenn die Konfiguration fehlt. Das ist besser als ein kryptischer Fehler später beim ersten API-Aufruf.
Wie kommt der Key in den Service?
Der SpotifyTokenClient, der den Authentifizierungstoken bei Spotify abholt, bekommt die SpotifyOptions über den Konstruktor injiziert — das ist Dependency Injection. Er muss sich nicht selbst um das Laden der Konfiguration kümmern:
public sealed class SpotifyTokenClient
{
private readonly HttpClient _httpClient;
private readonly SpotifyOptions _options;
public SpotifyTokenClient(HttpClient httpClient, SpotifyOptions options)
{
_httpClient = httpClient;
_options = options;
}
// Bei Bedarf: _options.ClientId und _options.ClientSecret nutzen
}
Die Vorteile dieses Ansatzes: Der Service kennt nicht die Herkunft der Konfiguration — ob sie aus einer Datei, Umgebungsvariablen oder einem Secret-Manager kommt, ist ihm egal. Außerdem kann man für Tests einen SpotifyOptions-Stub mit Testwerten übergeben, ohne eine echte Konfigurationsdatei zu brauchen.
Client-Credentials-Flow: keine Nutzerdaten nötig
EchoPlay verwendet den sogenannten Client-Credentials-Flow von Spotify. Das bedeutet: Die App meldet sich mit ihrer eigenen Identität an, nicht im Namen eines Benutzers. Es werden keine Spotify-Nutzerkonten, kein Login-Fenster und keine Weiterleitungen benötigt. Die App schickt ClientId und ClientSecret an Spotify und bekommt einen kurzlebigen Access Token zurück, den sie für alle weiteren API-Anfragen im Authorization-Header mitsenden muss.
Dieser Ablauf ist für reine Lesezugriffe auf öffentliche Katalogdaten gedacht — genau das, was EchoPlay braucht, um nach Hörspielserien zu suchen. Ein Nutzerkonto hätte keinen Vorteil, würde aber die Privatsphäre der Nutzer unnötig belasten.
Was ist der Unterschied zwischen Development und Production?
Development (Entwicklungsumgebung) bedeutet: die App läuft auf dem Rechner des Entwicklers. Hier kann man großzügiger sein — Testwerte in appsettings.Development.json, die Datei ist lokal und fliegt nie in Git.
Production (Produktionsumgebung) bedeutet: die App läuft bei echten Nutzern. Hier gelten strengere Regeln. Schlüssel dürfen nicht in Dateien stehen, die verteilt werden — sie kommen aus Umgebungsvariablen oder aus einem gesicherten Speicher, der nur auf dem Zielsystem verfügbar ist.
.NET unterstützt dieses Konzept durch die Umgebungsvariable DOTNET_ENVIRONMENT. Wenn sie auf Development gesetzt ist, lädt die App appsettings.Development.json. In Production lädt sie appsettings.Production.json oder verwendet Umgebungsvariablen.
Die Regel im Überblick
Ein API-Key gehört nie in den Quellcode. Er gehört in eine Konfigurationsdatei, die nicht im Repository liegt, oder in Umgebungsvariablen auf dem Zielsystem. In EchoPlay ist das durch appsettings.json (Struktur) und appsettings.Development.json (lokale Werte) gelöst. Die .gitignore-Datei sorgt dafür, dass Letztere nie versehentlich eingecheckt wird.
Die gezeigten Code-Beispiele dienen zur Veranschaulichung. Nutzung auf eigene Verantwortung. Mehr dazu