Thanks for your interest. VisuAuth is pre-alpha and the surface is moving — issues, discussion, and PRs are all welcome.
git clone https://github.com/VisuAuth/visuauth.git
cd visuauth
dotnet restore src/VisuAuth.slnx
dotnet build src/VisuAuth.slnx
dotnet test src/VisuAuth.slnxThe solution file lives in
src/VisuAuth.slnx. Open that one in Visual Studio or Rider.
Requires the .NET 10 SDK (10.0.300 or later, matching global.json).
src/VisuAuth.Abstractions/— Public contracts (IUserStore,IRoleStore,UserBackendCapabilities, etc.).src/VisuAuth.Identity/— ASP.NET Core Identity adapter (the default backend).src/VisuAuth.AdminUi/— Admin dashboard (Razor Pages + htmx).src/VisuAuth.EndUserUi/— Login, register, password reset, two-factor, external logins.src/VisuAuth/— Meta-package depending on the four above.src/VisuAuth.EntraCore/— Plumbing shared by both Entra adapters (Graph client, no-op stubs).src/VisuAuth.Entra/+src/VisuAuth.Entra.Web/— Entra ID (Workforce) adapter, and its operator OIDC sign-in.src/VisuAuth.EntraExternal/+src/VisuAuth.EntraExternal.Web/— Entra External ID (CIAM) adapter, and its customer OIDC sign-in.samples/Sample.WebApp/— Reference consumer, and the app the integration tests boot.samples/Sample.EntraWebApp/,samples/Sample.EntraExternalWebApp/— Minimal per-adapter references.
The .Web split keeps Microsoft.Identity.Web out of the core adapters, so
consumers who front the dashboard with their own authentication don't carry it.
VisuAuth uses trunk-based development: main is the only long-lived branch, and every change lands through a short-lived branch + pull request. We follow Conventional Commits for commit messages and PR titles.
<type>/<short-description>
<type>/<issue-number>-<short-description> # when an issue exists
| Prefix | Use for | Example |
|---|---|---|
feat/ |
New user-visible functionality | feat/admin-ui-user-search |
fix/ |
Bug fix | fix/identity-tenant-filter-leak |
docs/ |
Documentation only | docs/getting-started-guide |
refactor/ |
Restructure without behavior change | refactor/extract-user-mapper |
test/ |
Tests only | test/identity-adapter-coverage |
perf/ |
Performance improvement | perf/cache-tenant-resolver |
chore/ |
Maintenance, deps, configs | chore/bump-nuget-deps |
ci/ |
GitHub Actions changes | ci/add-codeql-scan |
build/ |
Build system, project files | build/enable-deterministic-builds |
release/ |
Release preparation | release/v0.1.0 |
hotfix/ |
Urgent fix from a published tag | hotfix/v0.1.1-jwt-validation |
Rules:
- Lowercase, hyphen-separated. No spaces, no underscores, no special chars.
- No personal names in branches (no
thiago/...). - Reference an issue number when one exists:
feat/42-add-totp-pages— GitHub auto-links it. - Keep the description under ~50 characters.
- Branches are short-lived. If a branch is open for more than a few days, rebase it on
mainto avoid drift.
<type>(<scope>): <subject>
<optional body>
<optional footer>
Types match the branch prefixes above. Scope is a short module name when useful (admin-ui, identity, abstractions, end-user-ui, ci, etc.). Subject is imperative, lowercase, no trailing period.
Examples:
feat(admin-ui): add user list page with pagination
fix(identity): apply tenant filter on bulk delete
refactor(abstractions): split IUserStore from IAuthenticationFlow
chore: bump aspnetcore to 10.0.1
docs(readme): document the drop-in flow
A breaking change is marked with ! and a BREAKING CHANGE: footer:
feat(identity)!: rename IUserStore.ListAsync to QueryAsync
BREAKING CHANGE: IUserStore.ListAsync was renamed to QueryAsync to match
the EF Core idiom. Adapters must be updated.
We use squash merge exclusively. Your PR's commits are squashed into a single commit on main whose message is the PR title (which should follow Conventional Commits). Keep the PR title clean and you don't have to worry about cleaning individual commits.
-
Nullable reference types are enabled and required.
-
Warnings as errors in
Releasebuilds. -
File-scoped namespaces always.
-
sealedby default unless inheritance is intentional. -
async/awaitalways withCancellationTokenfor any I/O. -
TimeProviderinstead ofDateTime.Now/DateTime.UtcNow. -
Records for DTOs, classes for behavior.
-
1 file = 1 public type.
-
usingdirectives are flat — no blank lines between them, no grouping by namespace prefix.dotnet_separate_import_directive_groups = falseenforces this in.editorconfig. Example:using System.Net; using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using VisuAuth.Identity.MultiTenancy; using Xunit;
Two test projects under tests/:
| Project | Purpose | Speed | Tooling |
|---|---|---|---|
VisuAuth.UnitTests |
Pure logic, no I/O, no host | < 5 ms / test | xUnit + FluentAssertions + Moq |
VisuAuth.IntegrationTests |
End-to-end via the sample app | 30–100 ms / test | + WebApplicationFactory<Program> |
Inside each project, sub-folders mirror src/ so the location of a test
hints at what it covers (Identity/Users/, Admin/, EndUser/, Api/,
MultiTenancy/, etc.).
Three underscored parts. This is the Microsoft-documented convention.
Unit tests use the method-under-test as the first part:
[Fact]
public void Generate_WithUppercaseRequired_IncludesAtLeastOneUppercase() { ... }
[Fact]
public void Success_DefaultArguments_ReturnsSuccessfulResultWithNullUserId() { ... }Integration tests treat the HTTP verb + endpoint name as the "method" (there's no single C# method under test — the endpoint is the unit):
[Fact]
public async Task PostLogin_WithWrongPassword_Returns401WithoutToken() { ... }
[Fact]
public async Task GetEditRole_OnExistingRole_RendersRowInEditMode() { ... }- Every bug fix ships with a regression test. New behaviour without a test is rejected.
- Security claims need a proving test. A comment or doc that asserts a security property ("validates the signature", "prevents open-redirect", "invalidates outstanding tokens") must be backed by a test that fails if the property is removed. See the security posture for the per-flow threats and where each is enforced and tested. A comment claiming a guarantee the code does not enforce is worse than none — it makes reviewers trust something that is not there.
- Tests must be order-independent. Never depend on the side effect of
another test. When a test mutates seeded data (rename, lockout, role
assignment), provision a throwaway user / role with a
Guid.NewGuid()- suffixed name so a re-run is safe. - Mocking lib is Moq. No NSubstitute, no FakeItEasy — one library so test patterns stay uniform.
- Use FluentAssertions'
because:argument to leave a note for the next reader:body.Should().Contain("Locked", "the badge must flip after the lock action"). - Internal API access — adapter projects expose
<InternalsVisibleTo Include="VisuAuth.UnitTests" />so the unit-test project can reach internal helpers (e.g.TemporaryPasswordGenerator) without leakingpubliconto the production surface. - Antiforgery in integration tests — Razor Pages require a token on
every POST. Tests GET the page first, parse the
__RequestVerificationTokenvalue with the shared regexname="__RequestVerificationToken"[^>]*?value="([^"]+)", and include it in the form body. - Cookies in integration tests — pass
WebApplicationFactoryClientOptions { HandleCookies = true }so the cookie jar survives between the GET that sets the antiforgery cookie and the POST that consumes it.
A Docker Compose stack with SonarQube Community ships in the repo so you can run the same analysis CI does (against SonarCloud) without leaving your machine. Steps:
docker compose -f docker-compose.sonar.yml up -d # one-time per session
scripts/sonar-local.ps1 # after non-trivial code changesFull instructions (token bootstrap, exclusions, dashboard URL) are in CLAUDE.md section 10.5. New bugs / vulnerabilities introduced by a PR should be fixed before requesting review.
- Check the issues list, not just the quality gate.
INFO/MINORviolations don't fail the gate but we keep them at zero. After a scan, query the issues — locallycurl -u "$SONAR_TOKEN:" "http://localhost:9000/api/issues/search?componentKeys=VisuAuth&inNewCodePeriod=true&resolved=false", or open the dashboard — and clear anything your change introduced before requesting review.
The user-facing promise is:
- Install
VisuAuthNuGet package. - Add
services.AddVisuAuth()...toProgram.cs. - Add
app.MapVisuAuth().
Nothing else. No Node.js, no build step on the consumer side, no extra middleware to configure manually.
When in doubt, optimize for the consumer's setup, not for VisuAuth internals.
Use the GitHub issue templates. Include:
- VisuAuth version
- .NET SDK version
- Minimal repro (a
dotnet new webproject that demonstrates the problem is gold) - Expected vs actual behavior
- Branch from
mainusing the naming convention above. - One concern per PR. Don't refactor unrelated code.
- PR title must follow Conventional Commits — it becomes the squashed commit message on
main. - Update tests. New behavior without a test is rejected.
- Update docs/README if user-facing behavior changes.
- If you add a VisuAuth route, link it from the sample home. Every new
page / endpoint must be reachable from
samples/Sample.WebApp/Program.cs(the/landing page), so the route is manually testable and nothing ships invisible. - Wait for CI to pass before requesting review.
- Triage every review comment after opening the PR and again after CI.
Read the conversation, the reviews, and the inline review comments
(
gh api repos/VisuAuth/VisuAuth/pulls/<N>/comments) — the automated reviewer leaves its findings inline, not in the main thread. Address or explicitly answer each one; never let an inline comment (especially a security finding) go unread.
Be respectful. We follow the Contributor Covenant v2.1 — see CODE_OF_CONDUCT.md for the full text and how to report a concern privately.
By contributing, you agree your contribution is licensed under the Apache License 2.0.