NPv.Auth.AspNetCore
0.6.1
dotnet add package NPv.Auth.AspNetCore --version 0.6.1
NuGet\Install-Package NPv.Auth.AspNetCore -Version 0.6.1
<PackageReference Include="NPv.Auth.AspNetCore" Version="0.6.1" />
<PackageVersion Include="NPv.Auth.AspNetCore" Version="0.6.1" />
<PackageReference Include="NPv.Auth.AspNetCore" />
paket add NPv.Auth.AspNetCore --version 0.6.1
#r "nuget: NPv.Auth.AspNetCore, 0.6.1"
#:package NPv.Auth.AspNetCore@0.6.1
#addin nuget:?package=NPv.Auth.AspNetCore&version=0.6.1
#tool nuget:?package=NPv.Auth.AspNetCore&version=0.6.1
NPv.Auth.AspNetCore
ASP.NET Core host integration for NPv authentication realms.
The package provides Minimal API auth endpoints, secure auth-cookie handling,
claims helpers, and dependency-injection registration for the application
workflows in NPv.Auth.Application.
Status
This package is at an early stage (0.x). Its public API, endpoint routes, and
cookie behavior may change between minor versions.
Installation
dotnet add package NPv.Auth.AspNetCore
Register a realm during application startup and map its endpoints in the host:
services.AddAuthRealm<Account, EmailConfirmation, PasswordRecovery, RefreshToken>(
new AuthRealmOptions
{
Name = "admin",
Path = "/api/admin/auth",
ClientBaseUrl = "https://admin.example.com",
RefreshCookieName = "a_rt",
DeviceCookieName = "a_di"
});
app.MapAuthRealm<Account>()
.AddEndpointFilter<NPv.AspNetCore.Endpoints.Validation.ValidationFilter>();
The host remains responsible for authentication middleware, authorization, configuration values, persistence registration, and database migrations.
For custom entities, follow the Domain contract and EF contract. The example assumes these consumer-defined types and host dependencies exist.
Repeat registration and endpoint mapping for each distinct account type, using unique realm names and separate endpoint paths and cookie names. The registry requires unique account types and names; registering one account type under multiple names does not create separate user pools.
ConfigurationKey defaults to Name. AddAuthRealm binds registration options
from Auth:Realms:{ConfigurationKey}:Registration; the host supplies other
AuthRealmOptions values explicitly, including ClientBaseUrl. See the
Application README for email-link configuration.
Use AddAuthClaimsProvider<TAccount, TProvider>() to replace the default claims provider.
Starting with 0.5.0, AddAuthRealm also registers
ICommand<ProvisionConfirmedAuthAccountCommandContext<TAccount>> with
ProvisionConfirmedAuthAccountCommand<TAccount> as a transient service. The host
authorizes the caller and invokes it through the standard CQS/UoW executor; no
provisioning HTTP endpoint is mapped. This supports confirmed account creation
even when public registration is disabled. Dependencies and caller obligations
are defined in the Application provisioning contract.
HTTP and session contract
This describes AspNetCore 0.6.0 with Application 0.5.0 and Foundation
Endpoints 0.2.0. All standard routes use POST under AuthRealmOptions.Path.
JSON below uses default ASP.NET Core naming; the host owns JSON configuration.
| Suffix | JSON request body | Success |
|---|---|---|
login |
{ "email": string, "password": string } |
200, { "email": string, "token": string, "roles": string[] } |
refresh |
None; both realm cookies required | 200, same body as login |
logout |
None; current refresh cookie, if present | 200, empty body |
register |
{ "email": string, "password": string, "confirmPassword": string } |
200, empty body |
confirm |
{ "confirmationCode": string } |
200, empty body |
recovery/request |
{ "email": string } |
200, empty body |
recovery/reset |
{ "recoveryCode": GUID-string, "password": string, "confirmPassword": string } |
200, empty body |
token is the access JWT; roles contains distinct role claims and may be empty.
Raw refresh tokens and their expiry are never returned in these HTTP bodies.
Registration, confirmation and recovery/reset do not log the caller in or set cookies.
Failures and host composition
Login, registration, confirmation and reset business rejections return 400 with
auth.invalid_credentials through Foundation ToIResult. Request validation
failures also return 400 through the Foundation filter. Refresh returns 401
with an empty body when either cookie is missing/empty, the refresh token is
unknown, expired or revoked, or its account is missing, banned or unconfirmed.
Treat login 400 as a rejected login and refresh 401 as an unusable session.
Refresh failure does not delete cookies. Infrastructure exceptions, HTTP binding
failures and host authentication/authorization responses are separate paths;
they are not promised to use the Auth business error response.
MapAuthRealm returns a RouteGroupBuilder; it does not attach validation,
authentication middleware or authorization policies, or override a host's fallback
policy. The host must compose validation as in the example above and explicitly
configure access to auth workflows and protected product routes.
With ValidationFilter, the standard bodies require valid email fields and
nonempty passwords/codes; registration/reset passwords are 6–16 characters and
confirmPassword must match. recoveryCode binds as a GUID and must identify a
valid recovery record. The generic registration overload binds the host's
TRegistrationRequest instead; its annotations and mapping are the host's
responsibility. The filter validates the bound request, not the command context
created later by that mapping. Direct command callers must supply validated input.
The Foundation endpoint contract
owns filter coverage, Result conversion, error envelopes, field-key casing and
validation-code mapping. Auth's standard annotations use default messages, so
their filter failures map to validation.invalid_value. The
Foundation CQS contract
owns execution/UoW and application-validation boundaries. Neither realm mapping
nor CQS execution replaces input validation.
Current workflow effects
- Login: requires a confirmed, non-banned account and matching password. It does not normalize email; send the stored normalized email. It issues a new refresh token and sets the device cookie only if it is missing/whitespace.
- Registration: trims and lowercases email.
Registration.Enabled = falserejects registration except for the configuredAdminEmail(case-insensitive match). Disabling registration does not remove the route. Existing confirmed or banned accounts are rejected. An unconfirmed account may retry after five minutes, replacing its password and confirmation code; earlier retries fail. Registration issues a confirmation code through the account lifecycle events. - Confirmation: requires a matching code no older than three days and an existing, non-banned, unconfirmed account. Success confirms the email and emits the confirmation event. A repeated confirmation fails; success is not login.
- Recovery request: trims and lowercases email. Missing, banned and
unconfirmed accounts receive the same empty success as eligible accounts.
An eligible account gets a new recovery code, replacing any previous one, and
an
AuthPasswordRecoveryRequestedEvent. Success does not prove account existence or email delivery; the host owns recovery-message delivery. - Reset: requires the current recovery code no older than three days and a confirmed, non-banned account. Success changes the password and deletes that recovery record. It does not revoke existing refresh sessions or access JWTs.
Current cookies and session lifetime
Cookie names are RefreshCookieName and DeviceCookieName; both use the realm
Path, Secure, HttpOnly and SameSite=None. Browser calls must send/accept
cookies (including credentials for cross-origin calls under the host's policy).
Access JWT lifetime is three minutes; browser storage follows the
session policy: memory only,
with refresh after reload.
Refresh requires both cookies, not an access JWT or a token in the request body. The device cookie alone is not a credential; current refresh checks its presence, not a server-side binding between device and token. A refresh token lasts seven days. Successful refresh rotates it once its age reaches six hours, revoking the old token and setting a new seven-day cookie; otherwise it keeps the existing token and expiry. Every successful refresh renews the device cookie for 365 days.
Logout revokes only the presented refresh token, if found, and deletes both realm cookies using their realm path. Missing/unknown/already revoked tokens still produce empty success. Logout is not logout-all and does not invalidate issued access JWTs; the client clears its in-memory access token.
These are current behaviors, not a new session-policy decision. Typed lifetime options, device lifecycle changes, forget-device and logout-all remain unfinished work in the existing Auth backlog.
Author's Note
This library grew out of my long-standing personal interest in structuring and publishing open source packages. Over time, I’ve revisited and refined earlier internal utilities and ideas, giving them a more consistent shape and preparing them for wider reuse. Along the way, I’ve also taken the opportunity to explore how open source distribution and licensing work in the .NET ecosystem.
It’s a small step toward something I’ve always wanted to try — sharing practical, minimal tools that reflect years of learning, experimentation, and refinement.
Hopefully, someone finds it useful.
Nikolai 😛
⚖️ License
MIT — you are free to use this in commercial and open-source software.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- NPv.AspNetCore.Endpoints (>= 0.2.0)
- NPv.Auth.Application (>= 0.5.1)
- NPv.Auth.Domain (>= 0.2.0)
- NPv.CQS.Abstractions (>= 3.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.