Files
SlipItIn/openwiki/architecture/overview.md
2026-08-08 12:50:15 +02:00

13 KiB

type, title, description, tags, openwiki
type title description tags openwiki
Architecture System Architecture & Security Overview 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.
architecture
spire
signalr
jwt
efcore
security
maui
mvvm
roles change_kinds source_paths symbols validation_commands
architecture
domain
lifecycle
public-api
SlipItIn/MauiProgram.cs
SlipItIn/Services/SignalRService.cs
SlipItIn/Services/GameStateService.cs
SlipItIn.Server/Program.cs
SlipItIn.Server/Hubs/GameHub.cs
SignalRService
GameStateService
LocalStorageService
GameHub
GameService
IDbContextFactory
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 .NET Aspire, while enforcing security & privacy on domain entities and serving real-time hub endpoints for all active game sessions. Source code structure for all architectural components can be found in the Source Code Map.


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<SlipItInDbContext>().
    • 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:

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

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.

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<TMessage> 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:

_hubConnection = new HubConnectionBuilder()
    .WithUrl(_configuration.HubUrl, options =>
    {
        options.AccessTokenProvider = () => Task.FromResult<string?>(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<SlipItInDbContext>:

  • 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:
    dotnet build SlipItIn.slnx