JWT access tokens · rotating refresh tokens · Argon2id · OpenID Connect · multi-tenant RBAC · direct asynchronous ADO.NET
Start in five minutes · Architecture · Security · Engineering evidence · Technical Wiki · Public API
Important
Release candidate: 0.9.0-rc.1 is for evaluation and integration testing. Stable 1.0.0 is a later, separately gated release.
SharpAccess is a Windows-only authentication and authorization package family for ASP.NET Core and .NET 10. It keeps security behavior in SharpAccess.Core and lets the host select exactly one supported relational persistence package.
It is designed for teams that want explicit security boundaries, deterministic migrations, bounded resource use, provider-neutral application code, exact-revision engineering evidence, and a small reviewed public API—without adopting an ORM, ASP.NET Identity, or a hidden application framework.
| Audience | What to inspect first |
|---|---|
| Recruiters and engineering leaders | Architecture, security model, quality snapshot, and release integrity. |
| Application engineers | Five-minute setup, package selection, and the Configuration. |
| Security reviewers | Security model, SECURITY.md, cryptography, OIDC, mutation, and supply-chain pages in the Wiki. |
| Operators | Platform contract, migrations, observability, recovery, capacity, and release runbooks in the Wiki. |
| Contributors | CONTRIBUTING.md, quality gates, provider contracts, public API baselines, and exact-revision verification. |
| Capability | Engineering contract |
|---|---|
| Authentication | Password sign-in, registration, email verification, reset, account-state validation, and fresh-authentication checks. |
| Sessions | Rotating opaque refresh tokens, replay detection, family revocation, bounded active families, and transactional audit evidence. |
| Authorization | Separate global and tenant roles/permissions, active-tenant binding, immutable tenant ownership, and stale-context invalidation. |
| Tokens | Rotatable access-token signing keys, keyed hashes for opaque tokens, bounded claims, issuer/audience/lifetime validation, and fail-closed kid handling. |
| OpenID Connect | Generic keyed providers, Authorization Code + PKCE, nonce/state validation, exact issuer/algorithm checks, endpoint allowlists, and one-time local exchange. |
| Persistence | Provider-neutral Core with direct asynchronous parameterized ADO.NET in the selected DB package. |
| Security | Argon2id, versioned peppers, bounded hashing concurrency, sanitized failures, CSRF controls, atomic security mutations, and explicit host-owned secrets. |
| Engineering | Windows/PowerShell-only verification, provider contracts, critical mutation invariants, SBOMs, checksums, recovery drills, and an offline deterministic quality report. |
SharpAccess.Core is the primary node. The host selects one supported DB package; Core never references a concrete database client.
flowchart TB
Core["SharpAccess.Core<br/>authentication · authorization · sessions · tokens · OIDC"]:::primary
Host["ASP.NET Core host<br/>Windows · .NET 10"] --> Core
Secrets["Host-owned secrets<br/>signing keys · peppers · token-hash keys"] --> Core
External["OIDC and email services"] <--> Core
Core -. provider-neutral contracts .-> Provider["One selected DB provider package<br/>connections · SQL · migrations · transactions"]
Provider --> DB[("Database")]
classDef primary fill:#512BD4,color:#fff,stroke:#2b176d,stroke-width:3px;
Dependency rules
SharpAccess.Coreowns provider-neutral behavior and contracts.- Provider packages reference Core, but never one another.
- Provider-specific SQL, migrations, transactions, connection handling, and error classification remain internal.
- Production persistence uses asynchronous parameterized ADO.NET and propagates cancellation.
- The host owns transport, trusted proxies, Data Protection topology, secret storage, logging, monitoring, and the selected DB connection.
Install Core plus exactly one supported DB provider.
| Package | Status | Role |
|---|---|---|
SharpAccess.Core |
Supported | Provider-neutral authentication, authorization, tokens, OIDC, diagnostics, middleware, endpoint mapping, and host integration. |
SharpAccess.Sqlite |
Supported | Zero-infrastructure reference and local/runtime DB provider. |
SharpAccess.Postgres |
Supported | Server DB provider with real-engine, migration, recovery, concurrency, query-plan, and package evidence. |
Local or embedded DB package set
dotnet add package SharpAccess.Core --version 0.9.0-rc.1
dotnet add package SharpAccess.Sqlite --version 0.9.0-rc.1Server DB package set
dotnet add package SharpAccess.Core --version 0.9.0-rc.1
dotnet add package SharpAccess.Postgres --version 0.9.0-rc.1Warning
Do not install or register both DB providers as primary persistence providers in the same host.
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddSharpAccess(builder.Configuration, options =>
{
options.Features.PasswordAuthentication = true;
options.Features.Registration = true;
options.Features.PasswordReset = true;
options.Features.RefreshTokens = true;
});
// Choose exactly one:
builder.Services.AddSqliteAccess(builder.Configuration);
// builder.Services.AddPostgresAccess(builder.Configuration);
WebApplication app = builder.Build();
await app.Services.InitializeSharpAccessAsync(
app.Lifetime.ApplicationStopping);
app.UseSharpAccessExceptionHandling();
app.UseSharpAccessSecurityHeaders();
app.UseSharpAccess();
app.MapSharpAccessEndpoints();
await app.RunAsync();Minimal configuration
{
"SharpAccess": {
"BaseUri": "https://app.example.com",
"JwtIssuer": "example-auth",
"JwtAudience": "example-clients",
"AccessTokenSigning": {
"ActiveKeyId": "2026-08",
"HmacSha256Keys": {
"2026-08": {
"Key": "<protected-secret>",
"ActivatedUtc": "2026-08-01T00:00:00Z"
}
}
},
"TokenHashing": {
"CurrentKeyVersion": "v1",
"Keys": {
"v1": "<protected-secret>"
}
},
"Passwords": {
"CurrentPepperVersion": "v1",
"Peppers": {
"v1": "<protected-secret>"
}
},
"RateLimits": {
"PartitionKey": "<dedicated-protected-secret>"
},
"Features": {
"PasswordAuthentication": true,
"Registration": true,
"PasswordReset": true,
"RefreshTokens": true,
"Administration": false,
"Tenancy": false
}
},
"ConnectionStrings": {
"Auth": "<host-owned DB connection string>"
}
}Caution
Never commit signing keys, token-hashing keys, password peppers, rate-limit partition keys, OIDC credentials, Data Protection certificates, or DB connection strings.
flowchart TD
Request["Incoming request"] --> Token["Validate algorithm · kid · issuer · audience · lifetime · bounded claims"]
Token --> Account["Recheck account state and security version"]
Account --> Authz["Recheck authorization version"]
Authz --> Scope{"Global or active tenant?"}
Scope -->|Global| Global["Global roles and permissions"]
Scope -->|Tenant| Tenant["Route-bound tenant roles, permissions, and owner"]
Global --> Policy["Explicit endpoint policy"]
Tenant --> Policy
Policy --> Result["Allow or fail closed"]
Security properties
- Argon2id passwords with random salts, versioned host-owned peppers, equivalent dummy work for unknown accounts, and bounded hashing capacity.
- Opaque refresh tokens; only keyed hashes are stored.
- Refresh rotation, replay detection, family revocation, security-version changes, and mandatory audit records are atomic where required.
- Global and tenant authorization are never flattened.
- Tenant claims must match the route-bound active tenant.
- OIDC uses Authorization Code + PKCE, nonce, state, exact issuer/algorithm checks, endpoint host allowlists, and one-time local exchange.
- Provider failures map to bounded provider-neutral categories; secrets and raw tokens are not logged.
The following block is generated from the exact release revision’s artifacts/quality-report/metrics.json. It must not be manually edited.
Source: exact-revision artifacts/quality-report/metrics.json · Schema: 2 · Enforcement: EvidenceOnly
| Metric | p95 | Worst observed | Aggregate / release interpretation |
|---|---|---|---|
| Line coverage | 100.00% | 0.00% minimum | 91.39% repository aggregate |
| Branch coverage | 100.00% | 0.00% minimum | 81.99% repository aggregate |
| CRAP score | 12.14 | 25.48 maximum | Executable methods only |
| Cyclomatic complexity | 8 | 24 maximum | Roslyn source metrics |
| Maintainability index | 100 | 41 minimum | Higher is better |
| Class coupling | 15 | 49 maximum | Distinct referenced types |
| Afferent coupling (Ca) | 11.6 | 13 maximum | Project + namespace units |
| Efferent coupling (Ce) | 8 | 9 maximum | Project + namespace units |
| Instability | 1.000 | 1.000 maximum | Ce / (Ca + Ce); informational |
| Critical mutation invariants | N/A | 0 survived; 0 infrastructure failures required | Binary per selected invariant; release tier must pass |
Scope and percentile notes
- Coverage percentiles are calculated across matched executable production members in
SharpAccess.Core,SharpAccess.Sqlite, andSharpAccess.Postgres. - The repository aggregate remains the release coverage score; the minimum exposes the worst observed member because coverage is higher-is-better.
- CRAP, cyclomatic complexity, maintainability index, and class-coupling statistics use the report's exact member dataset.
- Ca, Ce, and instability are calculated across project and namespace dependency units.
- Mutation invariants are binary and therefore do not have a meaningful p95.
- Consult
artifacts/quality-report/index.htmlandmetrics.jsonfor complete project, namespace, type, member, dependency, and hotspot detail.
For higher-is-better metrics such as coverage and maintainability, the table reports p95 and the minimum as the worst observed value. For adverse metrics such as CRAP, cyclomatic complexity, coupling, and instability, the worst value is the maximum. Critical mutation evidence is binary, so a percentile is not meaningful: every selected critical mutation must be killed, with zero infrastructure failures.
The complete report includes project, namespace, type, member, dependency, and hotspot views. See Quality and Metrics.
- Windows only.
- .NET 10 SDK selected by
global.json. - PowerShell 7 for repository automation.
- No Bash parity, Linux/macOS support claim, Dockerfile, Compose file, service container, or local container orchestration.
- External DB evidence uses native Windows tooling or an approved managed scratch DB.
- Hosts must validate their own deployment topology, availability, capacity, backups, monitoring, and disaster-recovery objectives.
The public repository begins with one signed root commit containing the exact tracked tree exported from an approved private development revision. It does not inherit private commits, branches, tags, notes, issues, pull-request refs, or Git objects.
Packages, symbols, SBOMs, checksums, provenance, the signed v0.9.0-rc.1 tag, and the GitHub prerelease are created only from the exact verified public release revision.
flowchart LR
Dev["Approved private revision"] --> Export["Deterministic tracked-file export"]
Export --> Root["Single signed public root"]
Root --> Verify["Exact public-root release matrix"]
Verify --> Tag["Signed v0.9.0-rc.1 tag"]
Tag --> Core["Publish SharpAccess.Core"]
Core --> Providers["Publish SharpAccess.Sqlite + SharpAccess.Postgres"]
Providers --> Release["GitHub prerelease + post-publication smoke"]
SQL Server and MySQL remain roadmap candidates only. They are not active projects, packages, registrations, APIs, tests, workflows, or support commitments.
SharpAccess is licensed under the MIT License.