Skip to content
NorseArchitecturePublic

About

Heimdall — the watchman who decides who crosses Bifrost. AuthN services for the Norse Architecture (Norse.AuthN.*): one set of access rules enforced identically across Blazor Server, WASM, and MAUI, with Blazor components for function and administration and the backing gRPC service.

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Heimdall

Heimdall — the watchman who decides who crosses Bifröst.

Heimdall — the ever-vigilant watchman of the gods, keeper of the Bifröst, whose sight and hearing know no limit

Image credit: @norsemythologyclips — go follow them.

The authn story for the Norse Architecture — Norse.AuthN: login, register, two-factor, recovery, and personal-data disclosure, enforced identically across Blazor Server, WASM, and MAUI, with the backing gRPC contracts. Heimdall rides on nothing above Asgard; Himinbjörg rides on it — implementing the contracts and hosting the pages — so the gate and the hall that stands behind it depend on each other by design.

What stands at the gate

Project What it is
Norse.AuthN.Services The wire tier, deliberately thin: IAuthenticationService (Login / Register / EmailExists / Logout, plus the account-lifecycle operations TwoFactorSignIn / RecoveryCodeSignIn / ForgotPassword / ResetPassword / ConfirmEmail / ConfirmEmailChange / ResendEmailConfirmation), IIdentityService (self-disclosure and masked disclosure), and IAccountService (self-service account management: GetAccountStatus / SetPhoneNumber / ChangePassword / SetPassword / ChangeEmail / GetExternalLogins / RemoveExternalLogin / DeletePersonalData), their pure [DataContract] wire records (the account ones: AccountStatusResponse, SetPhoneNumberRequest — whose Phone stamp is a nullable Result<PhoneNumber>?, null meaning clear — ChangePasswordRequest, SetPasswordRequest, ChangeEmailRequest, ExternalLoginsResponse with its ExternalLoginEntry/ExternalProviderEntry, RemoveExternalLoginRequest, DeletePersonalDataRequest), and the policy name constants (AuthNPolicies, IdentityPolicies). The issuance surface carries no result record of its own — every IAuthenticationService operation returns Asgard's Outcome<NavigationResult> or Outcome<BoolResponse> — while the disclosure surface answers with its own PersonalDataResponse/MaskedPersonalDataResponse and the account surface reads state through AccountStatusResponse/ExternalLoginsResponse. No Razor, no validation library, no mediator coupling — a consumer building their own UI references this assembly and nothing else
Norse.AuthN.Components The headless tier: pages with no visual framework (Logout, Lockout, AccessDenied, the confirmation pages), the FluentValidation validators (login, register, masked disclosure, and the seven account-lifecycle validators: two-factor, recovery code, forgot password, reset password, confirm email, confirm email change, resend confirmation, and the six account validators: set phone number, change password, set password, change email, remove external login, delete personal data), ManageStatus (the account-management redirect's status keys and the text each selects — the key crosses the query string, the text never does), and the session-transition seam (the form base OutcomeFormComponentBase and the ServerValidation machinery that projects server-side failures into the form's own validation display moved to Asgard's Abstractions.Components on 2026-08-09) (ISessionTransition/ForceLoadSessionTransition) — only the gate performs a forced document load for a sign-in change
Norse.AuthN.Components.FluentUI The visual skin: GateLayout, Login, Register, ModelValidationSummary, recovery codes, and the account-lifecycle pages — ForgotPassword, ResendEmailConfirmation, and five routable shims over plain-parameter units the story catalog renders directly: LoginWith2fa → TwoFactorForm, LoginWithRecoveryCode → RecoveryCodeForm, ResetPassword → ResetPasswordForm, ConfirmEmail → ConfirmEmailPanel, and ConfirmEmailChange → ConfirmEmailChangePanel. The two confirm panels are click-to-confirm: an emailed token is consumed on a click, never on load. The account-management tree sits beside them: ManageLayout and ManageNavMenu (a FluentNav/FluentNavItem menu) at the project root, and under Manage/ the loader-over-form pages — Index → ProfileForm, Email → ChangeEmailForm, ChangePassword and SetPassword → the shared PasswordPanel (ChangePasswordForm or SetPasswordForm, chosen by whether a password exists, with the title and heading following the chosen form; neither route redirects to the other), DeletePersonalData → DeletePersonalDataForm — plus ExternalLogins (its add-provider links target a server HTTP endpoint, not a routed page, so they set FluentLink's ForceLoad) and the moved PersonalData. Manage/_Imports.razor stamps every one of them with @layout ManageLayout and the Account authorization policy, and ManagePolicyLockTests fails the build's tests if a routed Manage page ever loses either. A different design system lands as a sibling package, not an edit

The dependency graph

Arrows point at the thing depended on. Himinbjörg rides above the gate — implementing its contracts and hosting its pages — which is why Heimdall isn't unambiguously topmost in the chain.

flowchart BT
	subgraph Himinbjorg["Himinbjörg"]
		IdentityWebServer["Identity.Web.Server"]
	end
	subgraph Heimdall["Heimdall — Norse.AuthN"]
		FluentUI["AuthN.Components.FluentUI"]
		Components["AuthN.Components"]
		Services["AuthN.Services"]
	end
	subgraph Asgard["Asgard"]
		AComponents["Abstractions.Components"]
		Contracts["Abstractions.Contracts"]
	end
	subgraph Svartalfheim["Svartálfheim"]
		Primitives["Norse.Primitives"]
	end
	IdentityWebServer --> FluentUI
	FluentUI --> Components
	Components --> Services
	Components --> AComponents
	Services --> Contracts
	Contracts --> Primitives
Loading

Dependencies are transitive-first by house law — Norse.Primitives reaches the wire records through Abstractions.Contracts, so no direct edge exists; versions are managed in one place, and the hosting composition root (Yggdrasil) pins the entire closure explicitly for deterministic builds and a single place to fix a vulnerable package. The one sanctioned break rides in AuthN.Services.csproj today: a direct floated System.Security.Cryptography.Xml overriding the known-vulnerable transitive version hosted by System.ServiceModel.Primitives.

How the gate works

The components never know what transport they're standing on. They inject the contract (IAuthenticationService), consume Task<Outcome<T>>, and pattern-match the result — success navigates, failure renders through the same validation display as client-side errors. Which implementation stands behind the contract is a per-host DI decision: Himinbjörg registers the real thing for Blazor Server, Yggdrasil wires the generated gRPC-Web client for WASM, and Bragi's story catalog registers a scripted fake. One component, three worlds, zero #if.

The requests are wire-stamped: the serialized email member is Result<EmailAddress> — the parse verdict itself rides the wire — while the form binds a raw buffer (EmailInput) that is never serialized and re-stamps the verdict on every assignment, so no code path sets the text without refreshing the parse. Deserialization is the parse event: the server holds its own verdict regardless of what the client claimed. Validators register their rules on the stamp, not the string — the parser owns format truth, the validator owns business truth — and the register flow chains an async email-existence lookup behind the shape gate, so unproven input never buys a round trip. Design: Glitnir's wire-stamped request scalars spec.

Validation is declared once and enforced twice: the same validator class runs client-side (Blazilla, against the wire record directly) and server-side (through Himinbjörg's generated adapter) — one declaration, never duplicated, so the two tiers cannot drift. And the wire records themselves are kept deliberately dumb: no authorization attributes, no mediator markers — a rejected login comes back as a typed failure (Failed(Problem)), never as a success record with a false flag. Success is Asgard's NavigationResult: a single server-resolved NextUrl the client navigates unconditionally (return URL, 2FA challenge, lockout — only the server knows the map). The issuance surface carries no result record of its own — the disclosure responses (PersonalDataResponse, MaskedPersonalDataResponse) are the wire tier's only response records, domain payloads rather than next hops.

Build and test

dotnet build Heimdall.slnx   # warnings are errors — a single warning fails
dotnet test Heimdall.slnx    # xUnit v3 + Shouldly on Microsoft.Testing.Platform

Requires the .NET 11 preview SDK pinned by global.json. The realm builds standalone — it is its own clone target, not only a Bifröst submodule. RequestContractTests is the wire tier's purity lock: no [Authorize] on wire records, no mediator-law assembly reference, a trailing CancellationToken on every service method, and [OperationContract] on every operation — a code-first gRPC method without it is a silently dead endpoint.

Status

The injection-clean pages live here; pages still coupled to server types (UserManager, HttpContext) remain in Himinbjörg's Identity.Web.Server and migrate over as they come clean — placement is a rule, not a list. Pages keep the ASP.NET Identity scaffold's /Account/* routes deliberately; renaming is a separate, deferred curation pass. Design for anything new happens first in Glitnir's docs/Heimdall/ — brainstorm → spec → plan, before any code.

The cosmos

Heimdall is one realm of the Norse Architecture. The whole platform composes at Bifröst — clone once, cross the bridge. Every design is tried in Glitnir, the design court, before code is forged here.

Soundtrack: Gjallarhorn

Soundtrack: Gjallarhorn

About

Heimdall — the watchman who decides who crosses Bifrost. AuthN services for the Norse Architecture (Norse.AuthN.*): one set of access rules enforced identically across Blazor Server, WASM, and MAUI, with Blazor components for function and administration and the backing gRPC service.

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages