--- type: Architecture title: System Architecture & Security Overview description: Technical architecture of SlipItIn, covering .NET Aspire orchestration, ASP.NET Core SignalR hub, .NET MAUI MVVM client layer, WeakReferenceMessenger event routing, and dual-layer local storage. tags: [architecture, spire, signalr, jwt, efcore, security, maui, mvvm] openwiki: roles: [architecture, domain] change_kinds: [lifecycle, public-api] source_paths: [SlipItIn/MauiProgram.cs, SlipItIn/Services/SignalRService.cs, SlipItIn/Services/GameStateService.cs, SlipItIn.Server/Program.cs, SlipItIn.Server/Hubs/GameHub.cs] symbols: [SignalRService, GameStateService, LocalStorageService, GameHub, GameService, IDbContextFactory] validation_commands: ["dotnet build SlipItIn.slnx"] --- # System Architecture & Security Overview **SlipItIn** is designed as a distributed, real-time application using modern .NET 10 architecture. This document details the orchestration model, backend server structure, .NET MAUI MVVM client architecture, real-time connection resilience, messaging infrastructure, security model, and concurrency safeguards. The architectural foundation [orchestrates services with](/openwiki/operations/runbook.md) .NET Aspire, while [enforcing security & privacy on](/openwiki/domain/game-mechanics.md) domain entities and [serving real-time hub endpoints for](/openwiki/workflows/slip-and-challenge.md) all active game sessions. Source code structure for all architectural components can be found in the [Source Code Map](/openwiki/source-map.md). --- ## 1. .NET Aspire Orchestration Model The solution uses **.NET Aspire** to orchestrate application resources, services, and infrastructure dependencies: - **AppHost (`SlipItIn.AppHost/AppHost.cs`)**: - Provisions a PostgreSQL database container (`AddPostgres("pgsql").AddDatabase("postgresdb")`). - Registers the backend project `SlipItIn.Server` with a direct reference to the PostgreSQL resource. - Registers the client project `SlipItIn` with service discovery references to `SlipItIn.Server`. - **Service Defaults (`SlipItIn.ServiceDefaults/Extensions.cs`)**: - Configures **OpenTelemetry** logging, metrics (AspNetCore, HttpClient, Runtime), and tracing. - Exposes standardized health check endpoints (`/health` and `/alive`). - Enforces automatic HTTP resilience (`AddStandardResilienceHandler()`) and service discovery. --- ## 2. ASP.NET Core Backend Architecture (`SlipItIn.Server`) The backend is an ASP.NET Core Web API & SignalR application providing REST endpoints for user management and real-time WebSockets for game state synchronization. ### Key Backend Components 1. **`Program.cs`**: - Registers `AddServiceDefaults()`, `AddNpgsqlDataSource("postgresdb")`, and `AddDbContextFactory()`. - Configures JWT Bearer authentication with custom `OnMessageReceived` token resolution for SignalR. - Automatically executes database migrations (`db.Database.Migrate()`) at startup. 2. **`AuthController.cs`**: - Manages user registration (`/api/auth/register`), authentication (`/api/auth/login`), and profile retrieval (`/api/auth/me`). - Uses `BCrypt.Net` for secure password hashing. - Issues JWT tokens signed with `Jwt:Key`, containing `ClaimTypes.NameIdentifier`, `ClaimTypes.Name`, and `ClaimTypes.Email`. 3. **`GameHub.cs`**: - Real-time SignalR hub mapped to `/hubs/game`. - Annotated with `[Authorize]` to reject unauthenticated WebSocket connections. - Extracts and verifies user claims, delegating state mutations to `IGameService`. 4. **`GameService.cs`**: - Implements core business logic: game creation, player joins, card dealing, slip submission, challenge creation, and resolution. - Adds `hostPlayer` directly to `game.Players` before `context.Games.Add(game)` to ensure EF Core navigation tracking consistency. --- ## 3. JWT Authentication & SignalR Security In real-time SignalR applications, clients can attempt to spoof player identifiers. SlipItIn eliminates this vulnerability through strict claim validation and authorization checks. ### SignalR Token Extraction Since WebSockets cannot pass custom HTTP headers during connection handshakes, `Program.cs` configures JWT query parameter extraction: ```csharp options.Events = new JwtBearerEvents { OnMessageReceived = context => { var accessToken = context.Request.Query["access_token"]; var path = context.HttpContext.Request.Path; if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs")) { context.Token = accessToken; } return Task.CompletedTask; } }; ``` ### Claims Validation & Access Control in `GameHub.cs` Inside `GameHub.cs`, claims are extracted directly from the authenticated SignalR context: * `GetAuthenticatedUserId()`: Extracts `Context.User?.FindFirst(ClaimTypes.NameIdentifier)`. Throws `UnauthorizedAccessException` if missing or invalid. * `ValidatePlayerAccessAsync(playerId, authUserId)`: Queries the database to verify that `Player.UserId` matches the authenticated user ID. * `ValidateHostAccessAsync(gameId, authUserId)`: Verifies that `Game.HostId` matches the authenticated user ID before allowing lobby configuration or game start. * `ValidateChallengeTargetAccessAsync(challengeId, authUserId)`: Ensures only the accused player can resolve a challenge against them. ### Real-Time Auth Flow Diagram ```mermaid sequenceDiagram autonumber actor Client as MAUI Client participant Auth as AuthController participant Hub as GameHub participant Service as GameService participant DB as SlipItInDbContext Client->>Auth: POST /api/auth/login Auth->>DB: Query User & Verify BCrypt Hash Auth-->>Client: 200 OK (JWT Access Token) Client->>Hub: Connect WebSocket /hubs/game?access_token=JWT Hub->>Hub: Validate JWT Signature & Claims Client->>Hub: JoinLobby(lobbyCode) Hub->>Hub: GetAuthenticatedUserId() Hub->>Service: JoinGameAsync(lobbyCode, userId, connectionId) Hub-->>Client: Broadcast PlayerJoined (GameStateDto) ``` *Sequence diagram showing user authentication via REST and subsequent authenticated SignalR WebSocket connection.* --- ## 4. .NET MAUI Client Architecture (`SlipItIn`) The cross-platform client app uses the **MVVM Pattern** built on `CommunityToolkit.Mvvm` and Dependency Injection configured in `MauiProgram.cs`. ```mermaid graph TD subgraph UI_Layer["UI Layer (Views)"] LoginPage["LoginPage"] RegisterPage["RegisterPage"] LobbyPage["LobbyPage"] GameBoardPage["GameBoardPage"] end subgraph ViewModel_Layer["ViewModel Layer"] LoginVM["LoginViewModel"] RegisterVM["RegisterViewModel"] LobbyVM["LobbyViewModel"] GameBoardVM["GameBoardViewModel"] end subgraph Messaging_Layer["Messaging Layer"] Messenger["WeakReferenceMessenger"] Messages["Messages (GameStateChanged, PlayerHandChanged, ChallengeReceived)"] end subgraph Service_Layer["Service Layer"] GameStateSvc["GameStateService"] SignalRSvc["SignalRService"] AuthSessionSvc["AuthSessionService"] ApiSvc["ApiService"] LocalStorageSvc["LocalStorageService"] end LoginPage --> LoginVM RegisterPage --> RegisterVM LobbyPage --> LobbyVM GameBoardPage --> GameBoardVM LobbyVM --> Messenger GameBoardVM --> Messenger GameStateSvc --> Messenger GameStateSvc --> SignalRSvc GameStateSvc --> LocalStorageSvc LoginVM --> ApiSvc LoginVM --> AuthSessionSvc LoginVM --> SignalRSvc ``` *Layered architecture diagram of the .NET MAUI client showing Views, ViewModels, WeakReferenceMessenger, and Service implementations.* ### Dependency Injection & Service Registration (`MauiProgram.cs`) - **Singletons**: `IAppConfigurationService` (`AppConfigurationService`), `ILocalStorageService` (`LocalStorageService`), `IAuthSessionService` (`AuthSessionService`), `IApiService` (`ApiService`), `ISignalRService` (`SignalRService`), `IGameStateService` (`GameStateService`), `LobbyViewModel`, `GameBoardViewModel`. - **Transients**: `LoginViewModel`, `RegisterViewModel`. - **Service Locator Helper**: `ServiceHelper.Services` captures `app.Services` at startup to allow static access where constructor DI is unavailable. ### Decoupled Messaging Infrastructure (`WeakReferenceMessenger`) To avoid memory leaks caused by long-lived event subscriptions in ViewModels, `GameStateService` translates raw `ISignalRService` hub events into strongly-typed messages dispatched via `WeakReferenceMessenger.Default`: - `LobbyCreatedMessage`: Dispatched when a new game lobby code is issued by the server. - `GameStateChangedMessage`: Dispatched when public game state or player list updates. - `GameStartedMessage`: Dispatched when the game transitions to `InProgress`. - `PlayerHandChangedMessage`: Dispatched when a player receives or updates private phrase cards. - `ChallengeReceivedMessage`: Dispatched when a player is directly targeted by a slip challenge. - `ErrorOccurredMessage`: Dispatched when a hub or network error occurs. - `ConnectionStateChangedMessage`: Dispatched when SignalR connection drops or reconnects. ViewModels implement `IRecipient` and register via `WeakReferenceMessenger.Default.RegisterAll(this)` to receive automated UI updates. ### Real-Time Connection Resilience & Auto-Reconnect `SignalRService` configures exponential backoff for WebSocket reconnection: ```csharp _hubConnection = new HubConnectionBuilder() .WithUrl(_configuration.HubUrl, options => { options.AccessTokenProvider = () => Task.FromResult(token); }) .WithAutomaticReconnect([TimeSpan.Zero, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30)]) .Build(); ``` When connection drops, `SignalRService` emits `ConnectionStateChanged`. Upon reconnection, `GameStateService` automatically triggers `ResyncAsync()` to fetch current state via `RequestGameStateAsync()` and flush locally queued offline actions. ### Dual-Layer Storage Security Model (`LocalStorageService`) To satisfy security and privacy constraints: 1. **`SecureStorage` (Platform Encrypted Storage)**: Used strictly for `AuthTokenKey` (`auth_token`). Prevents plaintext access to active JWT credentials. 2. **`Preferences` (Application Key-Value Settings)**: Used for non-sensitive cached data (`auth_user`, `game_state`, `player_hand`, and `queued_actions`). --- ## 5. Concurrency Safety: `IDbContextFactory` SignalR hubs process concurrent requests from multiple clients over persistent connections. Using a standard Scoped `DbContext` in SignalR leads to thread conflict exceptions (`InvalidOperationException: A second operation was started on this context instance before a previous operation completed`). ### Resolution via `IDbContextFactory` SlipItIn solves this by registering EF Core with `AddDbContextFactory`: * Every method call inside `GameHub.cs`, `AuthController.cs`, and `GameService.cs` creates a short-lived, isolated `DbContext` session using `using var context = _contextFactory.CreateDbContext()`. * **Benefit**: Thread-safe database operations across simultaneous challenges, card transfers, and lobby updates without race conditions. --- ## 6. Data Privacy Isolation Model To prevent players from inspecting opponent cards via network trace analysis, SlipItIn strictly separates public and private data DTOs: * **`GameStateDto` (Public)**: Broadcast to all players in a lobby. Contains public game information (`GameId`, `LobbyCode`, `Status`, `CurrentRound`, `RoundTimeRemaining`) and player summaries (`PlayerId`, `Username`, `Score`, `IsReady`, `CardCount`). **Crucially, no card text is included.** * **`PlayerHandDto` (Private)**: Direct unicast message sent *only* to the specific player's SignalR `ConnectionId`. Contains card text (`CardId`, `PhraseId`, `Text`, `IsUsed`). This architecture guarantees that players cannot see opponent phrase cards, even if they inspect raw WebSocket packets. --- ## 7. Guidance for Future Agents & Developers When extending or modifying the system architecture: - **When to Consult**: Consult this document before adding new real-time SignalR methods, introducing client ViewModels, modifying storage mechanisms, or altering server authentication logic. - **Runtime Invariants**: - `GameHub` methods must validate caller claims via `GetAuthenticatedUserId()` and `ValidatePlayerAccessAsync()`. - Client ViewModels must consume `WeakReferenceMessenger` messages rather than subscribing directly to `ISignalRService` C# events. - JWT tokens must never be persisted in `Preferences`; always use `SecureStorage`. - **Extension Points**: - New real-time server messages: Add method to `GameHub`, add event to `ISignalRService`, add message class in `SlipItIn/Messages/`, update `GameStateService` listener. - New ViewModels: Register as Transient (dialogs/auth) or Singleton (main navigation screens) in `MauiProgram.cs`. - **Primary Source Files**: - `SlipItIn/MauiProgram.cs` - `SlipItIn/Services/SignalRService.cs` - `SlipItIn/Services/GameStateService.cs` - `SlipItIn/Services/LocalStorageService.cs` - `SlipItIn.Server/Hubs/GameHub.cs` - `SlipItIn.Server/Services/GameService.cs` - **Focused Checks**: Build and verify project references across client, server, and shared libraries. - **Minimal Validation Command**: ```bash dotnet build SlipItIn.slnx ```