NcDanilo.UserLibrary
1.2.3
dotnet add package NcDanilo.UserLibrary --version 1.2.3
NuGet\Install-Package NcDanilo.UserLibrary -Version 1.2.3
<PackageReference Include="NcDanilo.UserLibrary" Version="1.2.3" />
<PackageVersion Include="NcDanilo.UserLibrary" Version="1.2.3" />
<PackageReference Include="NcDanilo.UserLibrary" />
paket add NcDanilo.UserLibrary --version 1.2.3
#r "nuget: NcDanilo.UserLibrary, 1.2.3"
#:package NcDanilo.UserLibrary@1.2.3
#addin nuget:?package=NcDanilo.UserLibrary&version=1.2.3
#tool nuget:?package=NcDanilo.UserLibrary&version=1.2.3
NcDanilo.UserLibrary
A .NET class library that provides ready-to-use services and repositories for user management, role management, email verification, and JWT-based authentication (access token + refresh token) backed by a SQL Server database.
Table of Contents
- What's New in 1.2.3
- Features
- Target Frameworks
- Installation
- Configuration
- Database Setup
- Getting Started
- HTTP Endpoints
- Project Structure
- Models & DTOs
- Requests & Responses
- Interfaces
- Validation Errors
- Dependencies
- License
What's New in 1.2.3
Email Verification
Users must verify their email address after registration before they can log in. A cryptographically-random 6-digit code is generated at registration time and must be submitted to GET /user/verify. Login is blocked with InvalidOperationException until verification is complete.
Admin-aware Registration
UserService now reads AdminEmail from configuration and automatically assigns the admin role when that email registers. All other users receive the user role. The role no longer needs to be passed in the registration request.
One-liner Dependency Injection
A new AddUserLibrary extension method registers all repositories, services, and controllers in a single call. The built-in UserController is also registered automatically via AddApplicationPart.
Automatic Database Initialization
DatabaseInitializer (registered as a singleton) creates the target database if it does not exist, creates all tables, and seeds the admin and user roles. The Users table now includes VerificationCode and IsVerified columns.
New REST Endpoints
PUT /user/status, PUT /user/password, DELETE /user, and GET /user/verify are all now exposed by the built-in UserController.
New Repository Method
GetUserNotVerified returns the list of users who have not yet completed email verification.
Features
- User management — create, read (by ID or email), update role, update password, soft-delete, list unverified users.
- Email verification — 6-digit code generated at registration; login blocked until verified.
- Role management — create, read, update, soft-delete roles;
admin/userseeded automatically. - JWT access tokens — signed with HMAC-SHA256, expire after 15 minutes, carrying standard claims (
id,email,given_name,surname,role). - Refresh tokens — persisted to SQL Server, expire after 7 days, support revocation.
- Password hashing — passwords are securely hashed with BCrypt before storage.
- Automatic DB setup —
DatabaseInitializercreates the database and all required tables on startup. - Fully async — all I/O operations are
async/awaitwithCancellationTokensupport. - Interface-driven — every component is hidden behind an interface, making it easy to mock and test.
Target Frameworks
| Framework | Version |
|---|---|
| .NET | 10.0 |
Installation
Install via the .NET CLI:
dotnet add package NcDanilo.UserLibrary
Or search for NcDanilo.UserLibrary in the Visual Studio NuGet Package Manager UI.
Configuration
The library reads its settings from an AppConfiguration object injected via IOptions<AppConfiguration>.
| Property | Description |
|---|---|
ConnectionString |
SQL Server connection string |
SecretKey |
Secret used to sign JWT tokens (≥ 32 characters recommended) |
Issuer |
JWT iss claim value |
Audience |
JWT aud claim value |
AdminEmail |
The email address that receives the admin role on first registration |
appsettings.json example
{
"AppConfiguration": {
"ConnectionString": "Server=localhost;Database=MyDb;Trusted_Connection=True;",
"SecretKey": "your-very-secret-key-at-least-32-chars",
"Issuer": "https://yourapp.com",
"Audience": "https://yourapp.com",
"AdminEmail": "admin@yourapp.com"
}
}
Database Setup
From v1.2.3, manual table creation is no longer required. Call
IDatabaseInitializer.Initialize()at startup and the library will create the database, all tables, and seed theadmin/userroles automatically. See Database Initialization.
If you prefer to manage the schema yourself, the expected tables are listed below.
Role
CREATE TABLE [dbo].[Role] (
[Id] UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
[RoleName] NVARCHAR(100) NOT NULL,
[Status] INT NOT NULL DEFAULT 0 -- 0 = Enabled, 1 = Disabled
);
Users
CREATE TABLE [dbo].[Users] (
[Id] UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
[FirstName] NVARCHAR(100) NOT NULL,
[LastName] NVARCHAR(100) NOT NULL,
[Email] NVARCHAR(200) NOT NULL UNIQUE,
[Password] NVARCHAR(200) NOT NULL, -- BCrypt hash
[Status] INT NOT NULL DEFAULT 0,
[BirthDate] DATE NOT NULL,
[RoleId] UNIQUEIDENTIFIER NOT NULL REFERENCES [dbo].[Role]([Id]),
[VerificationCode] INT NULL, -- 6-digit code, NULL after verification
[IsVerified] BIT NOT NULL DEFAULT 0,
[AccessTokenId] UNIQUEIDENTIFIER NULL,
[RefreshTokenId] UNIQUEIDENTIFIER NULL
);
RefreshToken
CREATE TABLE [dbo].[RefreshToken] (
[Id] UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
[UserId] UNIQUEIDENTIFIER NOT NULL REFERENCES [dbo].[Users]([Id]),
[Token] NVARCHAR(500) NOT NULL,
[Expiration] DATETIME2 NOT NULL,
[IsRevoked] BIT NOT NULL DEFAULT 0
);
Note: "Delete" operations on both
UsersandRolesare implemented as soft deletes — theStatuscolumn is set to1(Disabled) rather than removing the row.
Getting Started
Registering Services (Dependency Injection)
Use the AddUserLibrary extension method to register everything in one call. It configures AppConfiguration, registers all repositories and services as scoped, registers DatabaseInitializer as a singleton, and auto-discovers the built-in UserController.
// Program.cs
using UserLibrary.Statics;
builder.Services.AddUserLibrary(builder.Configuration);
Make sure app.MapControllers() is called so the built-in endpoints are routed.
Database Initialization
Call Initialize() once at application startup before handling requests:
// Program.cs
using UserLibrary.Interface;
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var dbInit = scope.ServiceProvider.GetRequiredService<IDatabaseInitializer>();
dbInit.Initialize();
}
app.Run();
Initialize() is idempotent — it uses IF NOT EXISTS guards and is safe to call on every startup.
User Registration
Registration no longer requires a role — it is assigned automatically based on AdminEmail in configuration.
var newUser = new UserDto
{
FirstName = "John",
LastName = "Doe",
Email = "john.doe@example.com",
Password = "P@ssw0rd!",
BirthDate = new DateTime(1990, 1, 1)
};
UserDto registered = await userService.RegisterUser(newUser, CancellationToken.None);
// A 6-digit VerificationCode has been generated and stored.
// The code must be sent to the user (e.g. via email) for them to verify.
The returned UserDto contains the VerificationCode so your application can forward it to the user. The Password field is always null in responses after hashing.
Email Verification
After registration the user must verify their email before logging in:
UserDto verified = await userService.VerifyEmail(
"john.doe@example.com",
123456, // the 6-digit code the user received
CancellationToken.None);
Console.WriteLine(verified.IsVerified); // true
Throws KeyNotFoundException if the email is not found, ArgumentException if already verified, or InvalidOperationException if the code does not match.
User Login
UserDto loggedIn = await userService.LoginUser(
"john.doe@example.com",
"P@ssw0rd!",
CancellationToken.None);
Console.WriteLine(loggedIn.AccessToken.Token); // JWT string
Console.WriteLine(loggedIn.AccessToken.Expiration); // UTC +15 minutes
Console.WriteLine(loggedIn.RefreshToken.Token); // Refresh token string
Throws:
UnauthorizedAccessException— invalid credentials.InvalidOperationException— user not yet verified, or token generation failure.ArgumentException— user account is disabled.
Update User Role
Requires admin JWT. Changes the role assigned to a user.
UserDto updated = await userService.UpdateUserStatus(
"john.doe@example.com",
"admin",
CancellationToken.None);
Update User Password
Requires admin JWT. Hashes and persists a new password.
UserDto updated = await userService.UpdateUserPassword(
"john.doe@example.com",
"NewP@ssw0rd!",
CancellationToken.None);
Delete User
Soft-deletes a user (sets Status = Disabled). Requires admin JWT.
UserDto deleted = await userService.DeleteUser(
"john.doe@example.com",
CancellationToken.None);
Console.WriteLine(deleted.Status); // Disabled
Role Management
// Create
var role = new Role { Id = Guid.NewGuid(), RoleName = "moderator", Status = Status.Enabled };
bool created = await roleRepository.CreateRole(role, CancellationToken.None);
// Read
Role fetched = await roleRepository.GetRoleById(role.Id, CancellationToken.None);
// Update
fetched.RoleName = "content-moderator";
bool updated = await roleRepository.UpdateRole(fetched, CancellationToken.None);
// Soft-delete (sets Status = Disabled)
bool deleted = await roleRepository.DeleteRole(role.Id, CancellationToken.None);
Token Validation
bool isValid = await manageTokenService.ValidateRefreshToken(
userDto.RefreshToken,
CancellationToken.None);
Returns true only when the token exists in the database, has not been revoked, and has not expired.
HTTP Endpoints
The built-in UserController exposes the following routes under /user:
| Method | Route | Auth Required | Description |
|---|---|---|---|
POST |
/user/login |
No | Authenticate and receive tokens |
POST |
/user/register |
No | Register a new user |
GET |
/user/verify |
No | Verify email with 6-digit code |
PUT |
/user/status |
Admin JWT | Change the role of a user |
PUT |
/user/password |
Admin JWT | Change the password of a user |
DELETE |
/user |
Admin JWT | Soft-delete a user |
All endpoints return 400 Bad Request with a list of UserValidationError objects when input validation fails.
Project Structure
UserLibrary/
├── Configuration/
│ └── AppConfiguration.cs # Settings POCO (connection string, JWT params, AdminEmail)
├── Controller/
│ └── UserController.cs # Built-in ASP.NET Core API controller
├── Dto/
│ ├── UserDto.cs # User data transfer object (includes VerificationCode, IsVerified)
│ └── RoleDto.cs # Role data transfer object
├── Enumeration/
│ └── UserEnumeration.cs # Typed validation error codes (UserValidationError)
├── Extentions/
│ └── UserMappingExtentions.cs # ToDto / ToEntity mapping extensions
├── Interface/
│ ├── IUserRepository.cs
│ ├── IRoleRepository.cs
│ ├── IRefreshTokenRepository.cs
│ ├── IUserService.cs
│ ├── IManageTokenService.cs
│ └── IDatabaseInitializer.cs
├── Model/
│ ├── User.cs # Includes VerificationCode, IsVerified
│ ├── Role.cs
│ ├── AccessToken.cs
│ ├── RefreshToken.cs
│ └── Status.cs # Enum: Enabled = 0, Disabled = 1
├── Repository/
│ ├── UserRepository.cs # Includes VerifyUser, GetUserNotVerified
│ ├── RoleRepository.cs
│ └── RefreshTokenRepository.cs
├── Request/
│ └── Request.cs # LoginRequest, RegisterRequest, VerifyUserRequest, …
├── Response/
│ └── Response.cs # LoginResponse, RegisterResponse, VerifyUserResponse, …
├── Service/
│ ├── UserService.cs
│ └── ManageTokenService.cs
├── Sql/
│ └── DatabaseInitializer.cs # Auto-creates DB, tables, and seeds roles
├── Statics/
│ ├── AddUserServices.cs # AddUserLibrary() extension method
│ ├── GenerateVerificationCode.cs # Cryptographically-random 6-digit code generator
│ └── TokenHelper.cs # JWT generation utility
└── Validator/
└── UserValidator.cs # Static validators for all request types
Models & DTOs
User / UserDto
| Property | Type | Notes |
|---|---|---|
Id |
Guid |
Auto-generated on registration |
FirstName |
string |
|
LastName |
string |
|
Email |
string |
Used as login identifier |
Password |
string |
BCrypt hashed; null in responses |
BirthDate |
DateTime |
|
Status |
Status |
Enabled or Disabled |
VerificationCode |
int? |
6-digit code; null after verification |
IsVerified |
bool |
false until VerifyEmail is called |
Role |
Role/RoleDto |
Auto-assigned based on AdminEmail config |
AccessToken |
AccessToken |
Populated after login |
RefreshToken |
RefreshToken |
Populated after login |
AccessToken
| Property | Type | Notes |
|---|---|---|
Token |
string |
JWT string |
Expiration |
DateTime |
UTC, 15 minutes from issue |
RefreshToken
| Property | Type | Notes |
|---|---|---|
Id |
Guid |
|
UserId |
Guid |
|
Token |
string |
GUID-based opaque string |
Expiration |
DateTime |
UTC, 7 days from issue |
IsRevoked |
bool |
Requests & Responses
Requests
| Class | Fields |
|---|---|
LoginRequest |
email, password |
RegisterRequest |
email, password, firstname, lastname, birthdate |
VerifyUserRequest |
email, verificationCode |
UpdateUserStatusRequest |
email, role |
UpdateUserPasswordRequest |
email, password |
DeleteUserRequest |
email |
Responses
| Class | Fields |
|---|---|
LoginResponse |
email, token (AccessToken), refreshToken |
RegisterResponse |
email, rolename, firstname, lastname, birthdate |
VerifyUserResponse |
email, isVerified |
UpdateUserStatusResponse |
email, role |
UpdateUserPasswordResponse |
email, password |
DeleteUserResponse |
email, status |
Interfaces
| Interface | Responsibility |
|---|---|
IUserRepository |
CRUD + verify + list-unverified on Users table |
IRoleRepository |
CRUD operations on the Role table |
IRefreshTokenRepository |
Persist and validate refresh tokens |
IUserService |
High-level register / verify / login / update / delete flow |
IManageTokenService |
Generate and validate access & refresh tokens |
IDatabaseInitializer |
Create database schema and seed initial data |
Validation Errors
All validation errors are returned as a list of UserValidationError objects with a Code and Message.
| Code | Description |
|---|---|
EMAIL_REQUIRED |
Email field is missing |
INVALID_EMAIL_FORMAT |
Email does not match username@example.com pattern |
EMAIL_NOT_FOUND |
No user found with the given email |
PASSWORD_REQUIRED |
Password field is missing |
PASSWORD_TOO_SHORT |
Password is shorter than the minimum length |
INVALID_PASSWORD_FORMAT |
Password must be ≥ 8 chars with uppercase, lowercase, digit, and symbol |
FIRSTNAME_REQUIRED |
First name is missing |
FIRSTNAME_TOO_LONG |
First name exceeds maximum length |
LASTNAME_REQUIRED |
Last name is missing |
LASTNAME_TOO_LONG |
Last name exceeds maximum length |
BIRTHDATE_REQUIRED |
Birth date is missing |
INVALID_BIRTHDATE |
Birth date is not a valid date |
ROLE_REQUIRED |
Role field is missing |
INVALID_ROLE |
Role must be admin or user |
INVALID_CODE_FORMAT |
Verification code must be exactly 6 digits |
INVALID_USER_ID |
Request body is null or malformed |
Dependencies
| Package | Version | Notes |
|---|---|---|
BCrypt.Net-Next |
4.1.0 | Password hashing |
Microsoft.Data.SqlClient |
7.0.0 | SQL Server connectivity |
Newtonsoft.Json |
13.0.4 | JSON serialization |
Microsoft.Extensions.Options |
10.0.5 | Options pattern |
Microsoft.Extensions.Options.ConfigurationExtensions |
10.0.5 | Config binding |
Microsoft.Extensions.Configuration.Binder |
10.0.5 | Configuration binding |
Microsoft.AspNetCore.App |
(framework ref) | ASP.NET Core integration |
License
This project is licensed under the MIT License — see LICENSE for details.
| 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
- BCrypt.Net-Next (>= 4.1.0)
- Microsoft.Data.SqlClient (>= 7.0.0)
- Newtonsoft.Json (>= 13.0.4)
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 |
|---|---|---|
| 1.2.3 | 113 | 3/27/2026 |
| 1.2.2 | 97 | 3/26/2026 |
| 1.2.1 | 96 | 3/25/2026 |
| 1.2.0 | 95 | 3/25/2026 |
| 1.1.3 | 93 | 3/25/2026 |
| 1.1.2 | 97 | 3/24/2026 |
| 1.1.1 | 94 | 3/24/2026 |
| 1.1.0 | 100 | 3/24/2026 |
| 1.0.10 | 112 | 3/24/2026 |
| 1.0.9 | 94 | 3/23/2026 |
| 1.0.7 | 95 | 3/23/2026 |
| 1.0.6 | 96 | 3/23/2026 |
| 1.0.5 | 98 | 3/23/2026 |
| 1.0.4 | 93 | 3/23/2026 |
| 1.0.3 | 99 | 3/23/2026 |
| 1.0.2 | 97 | 3/23/2026 |
| 1.0.1 | 98 | 3/23/2026 |
| 1.0.0 | 96 | 3/23/2026 |