365Architect

CipherShift365 — Blazor CISO Portal

Component: CipherShift365.Portal.Web
Framework: Blazor Server (.NET 10.0)
Render mode: InteractiveServerRenderMode
Auth: JWT (via REST API)
Accessibility: WCAG 2.1 AA target


What It Does

The Blazor CISO Portal is the user-facing web application for the CipherShift365 platform. It provides Chief Information Security Officers, security architects, and compliance auditors with a single-pane-of-glass view over their cryptographic estate's post-quantum readiness. Every data point is read from the REST API /v1 — the portal has zero direct access to files, databases, or cryptographic material.

Key features:

  • Sign-in with role selection (no passwords — engagement-tier design)
  • Posture dashboard with risk score cards and finding counts
  • Findings explorer with filterable/sortable table (risk, role, confidence, free-text search)
  • Migration plan viewer with urgency, effort estimates, and rationale
  • CNSA 2.0 verdict page with compliant/non-compliant breakdown and gap analysis
  • Report download in HTML, Markdown, and branded PDF formats
  • Questionnaire assessment with scoring and recommendations
  • WCAG accessibility via semantic HTML, aria-label attributes, and dark-mode support
  • Role-driven views — navigation links and action buttons appear/disappear based on role

Why It's Needed (Value Proposition)

Need How the portal addresses it
Executive visibility CEO/Executive role sees dashboards, scores, and reports without technical noise.
Consultant-led engagements ConsultantAdmin role can trigger scans, render PDFs, view audit trails, and submit assessments — all from the browser.
Compliance evidence Auditor role sees findings, CNSA verdicts, and can take assessments to document readiness.
Zero-install access Runs in any modern browser. No desktop software, no PowerShell, no CLI required for leadership.
Single source of truth The portal reads exclusively from the REST API. No data discrepancies between curl output and dashboard.
Air-gapped ready Works on an isolated network — no CDN scripts, no external fonts, no telemetry.

Installation

Prerequisites

Requirement Minimum
.NET SDK 10.0
OS Windows 10+ / Windows Server 2019+ / Linux x64
Browser Chrome 100+, Edge 100+, Firefox 100+, Safari 16+
REST API Running at http://localhost:5000 (or configured base URL)
Network None external — portal-to-API is loopback or same-network only

Build & Run

BASH
cd src/Portal/CipherShift365.Portal.Web
dotnet restore

# Set the API base URL (defaults to http://localhost:5000)
$env:ApiBaseUrl = "http://localhost:5000"

dotnet run --urls http://localhost:5001

The portal starts at http://localhost:5001. Open it in a browser.

Configuration

All configuration lives in appsettings.json:

JSON
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ApiBaseUrl": "http://localhost:5000"
}
Setting Description Default
ApiBaseUrl The REST API base URL the portal calls http://localhost:5000
AllowedHosts Host header filtering *

For deployment, set ApiBaseUrl via environment variable:

BASH
# Windows
$env:ApiBaseUrl = "https://api.internal.example.com"

# Linux
export ApiBaseUrl=https://api.internal.example.com

How to Run / Use

Starting the Full Stack

BASH
# Terminal 1 — Start the API
cd src/Portal/CipherShift365.Portal.Api
dotnet run --urls http://localhost:5000

# Terminal 2 — Start the Portal
cd src/Portal/CipherShift365.Portal.Web
$env:ApiBaseUrl = "http://localhost:5000"
dotnet run --urls http://localhost:5001

Open http://localhost:5001 in a browser.

Sign-In Flow

  1. The landing page shows three role cards: Executive, Consultant Admin, Auditor.
  2. Click a card — the portal calls POST /v1/auth/token with the corresponding clientId and receives a JWT.
  3. The JWT is stored in-memory in PortalUserState and attached as Authorization: Bearer to every subsequent API call.
  4. The sidebar navigation updates dynamically based on role capabilities.
  5. To switch roles, click Sign Out and choose a different card.

There are no passwords. The engagement-tier design uses client ID as the sole identity assertion. In production, replace with real credential validation or OIDC federation.


User Manual

Page Reference

Page Route Who sees it What it does
Sign In /signin Everyone (unauthenticated) Role selection
Dashboard / All authenticated users Score cards, finding summaries, scan trigger
Findings /findings All authenticated users Filterable cryptographic findings table
Plan /plan All authenticated users Prioritized migration plan table
CNSA 2.0 /cnsa All authenticated users CNSA compliance verdict and gap analysis
Reports /reports Executive, ConsultantAdmin HTML preview, Markdown/PDF download
Questionnaire /questionnaire ConsultantAdmin, Auditor PQC readiness assessment

Role-Driven Views

The navigation menu adapts to the authenticated role:

Feature Executive ConsultantAdmin Auditor
Dashboard Visible Visible Visible
Findings Explorer Visible Visible Visible
Migration Plan Visible Visible Visible
CNSA Verdict Visible Visible Visible
Reports (download) Visible Visible Hidden
Questionnaire Hidden Visible Visible
Run Scan (button) Hidden Visible Hidden
Audit Trail (API only) Hidden Visible Hidden

This is enforced at two levels:

  1. Portal UI layerPortalUserState.CanScan / CanAudit / CanViewReports control link and button visibility.
  2. API authorization layer — The REST API independently validates the JWT role claim against EndpointPermissions.

A compromised client cannot escalate privileges because the API rejects unauthorized requests with 403 Forbidden.

Dashboard

The posture dashboard at / displays:

  • Six score cards showing the distribution of findings across risk categories: Quantum Broken (red), Quantum Vulnerable (orange), Mitigable (amber), Quantum Safe (green), Total Findings, and System Score.
  • A findings summary table with algorithm, role, risk, confidence, and source location.
  • A "Run Scan" button (ConsultantAdmin only) that triggers POST /v1/scans and refreshes the data.

All data is fetched from GET /v1/findings?project=. on page load.

Findings Explorer

The findings explorer at /findings provides an interactive filterable table:

  • Risk filter — dropdown for QuantumBroken, QuantumVulnerable, Mitigable, QuantumSafe
  • Role filter — dropdown for Signature, KeyExchange, Kem, Symmetric, Hash, Mac
  • Confidence filter — dropdown for High, Medium, Low
  • Free-text search — type an algorithm name to filter instantly

Filters are applied client-side over the in-memory JSON response — no additional API calls.

Migration Plan

The migration plan at /plan shows:

  • Total effort in person-days and item count
  • A prioritized table with urgency, current algorithm, target PQC algorithm, effort in days, and rationale

Urgency levels are color-coded: Critical (red), High (orange).

CNSA 2.0 Compliance

The CNSA verdict at /cnsa displays:

  • A verdict banner — green for Compliant, red for NonCompliant
  • Compliant / NonCompliant counts
  • Gap analysis table — for each gap: algorithm, gap description, recommended action
  • Evidence footer — profile name and version used for validation

Reports

The reports page at /reports offers three download actions:

  1. HTML Report — fetches GET /v1/reports?project=.&format=html and renders it in an embedded <iframe>.
  2. Markdown Report — fetches the Markdown version and displays it as text.
  3. Branded PDF — calls POST /v1/reports/render and triggers a browser download via window.downloadFile() in app.js.

Only users in the Executive or ConsultantAdmin role see this page.

Questionnaire

See the Questionnaire Engine documentation for details. The portal page:

  • Loads the pqc-readiness-v1 template from GET /v1/questionnaire/templates/pqc-readiness-v1
  • Renders each section with questions and appropriate input types (radio groups, text inputs)
  • Computes client-side scores for immediate feedback
  • Submits via POST /v1/questionnaire/submit and displays the scored result with recommendations

WCAG Accessibility

The portal targets WCAG 2.1 Level AA compliance:

Criterion Implementation
1.1.1 Non-text Content All interactive elements have text labels. Score cards use <dl>/<dt>/<dd> semantic structure.
1.3.1 Info and Relationships Tables use <thead>, <tbody>, <th scope>. Forms use <label> wrappers. Navigation is an <nav> landmark with aria-label.
1.4.3 Contrast (Minimum) Color palette (:root custom properties) meets 4.5:1 for normal text. Dark mode is supported via prefers-color-scheme: dark.
1.4.4 Resize Text Relative units (rem, em) throughout. Page zooms to 200% without horizontal scroll.
2.1.1 Keyboard All buttons and links are natively keyboard-operable. No custom widgets trap focus.
2.4.1 Bypass Blocks Main content is in <main role="main">. Sidebar uses <nav aria-label="Main navigation">.
2.4.2 Page Titled Each page has an <h1> describing its purpose.
3.3.2 Labels or Instructions All inputs have associated labels. Question options have explicit radio <label> wrappers.
4.1.2 Name, Role, Value Standard HTML elements used throughout — <button>, <select>, <input>, <table>.

Color and contrast key:

  • --crit / --warn / --ambr / --safe distinguish risk levels and meet non-color-redundancy requirements (all are also conveyed via text labels).
  • Focus indicators: .btn-primary:focus-visible draws a 2px outline with offset.

Developer Manual

Architecture

Rendering diagram...

Project Structure

CipherShift365.Portal.Web/
├── Program.cs                    # Host builder, DI registration
├── PortalServices.cs             # PortalUserState, PortalAuthStateProvider, ApiClient
├── Components/
│   ├── App.razor                 # Root component
│   ├── Routes.razor              # Route table
│   ├── _Imports.razor            # Shared usings
│   ├── Layout/
│   │   ├── MainLayout.razor      # Sidebar + main content shell
│   │   ├── MainLayout.razor.css  # Layout-specific styles
│   │   ├── NavMenu.razor         # Bootstrap-based menu (legacy, not primary)
│   │   ├── NavMenu.razor.css
│   │   ├── ReconnectModal.razor  # Blazor circuit reconnect UI
│   │   ├── ReconnectModal.razor.css
│   │   └── ReconnectModal.razor.js
│   ├── Pages/
│   │   ├── SignIn.razor          # Role card selection
│   │   ├── Dashboard.razor       # Posture dashboard + scan trigger
│   │   ├── Findings.razor        # Filterable findings explorer
│   │   ├── Plan.razor            # Migration plan viewer
│   │   ├── CnsaVerdict.razor     # CNSA compliance verdict
│   │   ├── Reports.razor         # Report downloads (HTML/MD/PDF)
│   │   ├── Questionnaire.razor   # Assessment UI
│   │   └── NotFound.razor        # 404 fallback
│   └── Shared/                   # Shared components (empty — available for extension)
├── wwwroot/
│   ├── app.css                   # Full application stylesheet
│   ├── app.js                    # JS interop (file download)
│   └── favicon.png               # Tab icon
├── appsettings.json
├── appsettings.Development.json
└── CipherShift365.Portal.Web.csproj

Authentication Flow (Portal Side)

  1. User clicks a role card → SignIn.razor calls ApiClient.SignInAsync(clientId).
  2. SignInAsync POSTs to /v1/auth/token, parses the JWT from the response.
  3. The JWT is stored in PortalUserState.Token.
  4. PortalAuthStateProvider.SignIn(...) populates claims and calls NotifyAuthenticationStateChanged(...).
  5. Blazor's [Authorize] attribute on pages and the <AuthorizeView> component react to the auth state.
  6. ApiClient.SetAuth() attaches Authorization: Bearer <token> to every subsequent HTTP call.

Adding a New Page

  1. Create a .razor file in Components/Pages/.
  2. Add @page "/your-route" at the top.
  3. Add @attribute [Authorize] if authenticated access is required.
  4. Add a role guard if needed:
RAZOR
@if (AuthState.State.CanViewReports) { ... }
  1. Add the navigation link in MainLayout.razor:
RAZOR
<NavLink href="/your-route">Your Page</NavLink>

Adding a New API Call

Extend ApiClient in PortalServices.cs:

CSHARP
public async Task<JsonDocument?> GetSomethingAsync()
{
    SetAuth();
    var response = await _http.GetAsync("/v1/something");
    if (!response.IsSuccessStatusCode) return null;
    var json = await response.Content.ReadAsStringAsync();
    return JsonDocument.Parse(json);
}

Styling

All styles are in wwwroot/app.css. The design system uses CSS custom properties:

Variable Purpose Light default Dark default
--bg Page background #f8f9fa #1a1a2e
--fg Text color #1a1a1a #e0e0e0
--accent Brand color / links / headers #1a3a5c #4a8fd4
--crit QuantumBroken / Critical urgency #c0392b #e74c3c
--warn QuantumVulnerable / High urgency #d35400 #f39c12
--ambr Mitigable #b8860b #f1c40f
--safe QuantumSafe / Compliant #27ae60 #2ecc71
--border Table/input borders #ddd #444
--card Card background #fff #16213e

The layout uses a sidebar + main content pattern via CSS flexbox (display: flex). At widths below 768px, the layout switches to vertical stacking.

JavaScript Interop

The only JavaScript interop is in app.jswindow.downloadFile(filename, mimeType, base64) — which creates a temporary <a> element with a data URI and triggers a click. It is called from Reports.razor for PDF download.

Code Examples

Custom Role-Guarded Component

RAZOR
@page "/admin-panel"
@attribute [Authorize]
@inject PortalAuthStateProvider AuthState

@if (AuthState.State.CanScan)
{
    <button class="btn-primary" @onclick="DoAdminAction">Admin Action</button>
}
else
{
    <p>This section requires ConsultantAdmin privileges.</p>
}

Raw API Data Display

RAZOR
@page "/debug"
@attribute [Authorize]
@inject ApiClient Api

<h1>Raw Scores</h1>

@if (_json is not null)
{
    <pre>@_json.RootElement.GetRawText()</pre>
}

@code {
    private JsonDocument? _json;
    protected override async Task OnInitializedAsync()
    {
        _json = await Api.GetAsync("/v1/scores?project=.");
    }
}

Programmatic Scan Trigger

RAZOR
@inject ApiClient Api

<button @onclick="RunScan">Start Scan</button>
<p>@_status</p>

@code {
    private string _status = "";

    private async Task RunScan()
    {
        _status = "Scanning...";
        var result = await Api.PostAsync("/v1/scans", new { project = "." });
        if (result is not null)
        {
            _status = $"Scan {result.RootElement.GetProperty("status").GetString()} — {result.RootElement.GetProperty("findings").GetInt32()} findings";
        }
    }
}

Configuration Options

Setting File Default Description
ApiBaseUrl appsettings.json http://localhost:5000 REST API base URL the portal targets
Logging:LogLevel:Default appsettings.json Information Log verbosity
AllowedHosts appsettings.json * Host header filtering
BlazorDisableThrowNavigationException csproj true Suppresses navigation exceptions for 404 handling

Third-Party Dependencies

Package Version License Use
Microsoft.AspNetCore.Components.Authorization (framework) MIT Blazor auth state management
Microsoft.Extensions.Http (framework) MIT Typed HttpClient via DI
CipherShift365.Core.Runtime (project ref) Proprietary Shared type references

The portal has no NuGet package dependencies beyond the .NET 10.0 framework — all rendering is standard Blazor. No Bootstrap, no MudBlazor, no JavaScript framework.


Design Decisions & Rationale

Decision Rationale
Blazor Server (not WASM) No WebAssembly download for air-gapped networks. SignalR circuit is local (same network as API).
No CSS framework ~200 lines of custom CSS with dark mode — no build pipeline for SCSS/SASS, no node_modules.
Singleton PortalUserState Single user per server process (engagement-tier design). Multi-user deployments would use scoped services or per-circuit state.
API-only data access No direct database or file access. The portal is a UI shell — all logic lives in the API.
JsonDocument over typed models Avoids duplicating server-side DTOs in the Blazor project. Trade-off: no compile-time type safety in Razor code.
No anti-forgery for API calls Anti-forgery is enabled for Blazor forms but API calls use JWT bearer tokens (not cookies).
Share

Keyboard Shortcuts

⌘ K
Open search
/
Focus search
?
Show shortcuts
b
Toggle bookmark
Alt+←
Previous page
Alt+→
Next page
Esc
Close overlay