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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="NPv.Auth.AspNetCore" Version="0.6.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="NPv.Auth.AspNetCore" Version="0.6.1" />
                    
Directory.Packages.props
<PackageReference Include="NPv.Auth.AspNetCore" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add NPv.Auth.AspNetCore --version 0.6.1
                    
#r "nuget: NPv.Auth.AspNetCore, 0.6.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package NPv.Auth.AspNetCore@0.6.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=NPv.Auth.AspNetCore&version=0.6.1
                    
Install as a Cake Addin
#tool nuget:?package=NPv.Auth.AspNetCore&version=0.6.1
                    
Install as a Cake Tool

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 = false rejects registration except for the configured AdminEmail (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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.6.1 4 10/25/2026
0.6.0 126 9/13/2026
0.5.0 107 9/13/2026
0.4.0 157 8/17/2026
0.3.1 121 8/6/2026
0.3.0 117 8/6/2026
0.2.1 120 8/5/2026
0.2.0 137 8/3/2026
0.1.0 124 8/1/2026