SiLA2.Authentication
10.2.2
dotnet add package SiLA2.Authentication --version 10.2.2
NuGet\Install-Package SiLA2.Authentication -Version 10.2.2
<PackageReference Include="SiLA2.Authentication" Version="10.2.2" />
<PackageVersion Include="SiLA2.Authentication" Version="10.2.2" />
<PackageReference Include="SiLA2.Authentication" />
paket add SiLA2.Authentication --version 10.2.2
#r "nuget: SiLA2.Authentication, 10.2.2"
#:package SiLA2.Authentication@10.2.2
#addin nuget:?package=SiLA2.Authentication&version=10.2.2
#tool nuget:?package=SiLA2.Authentication&version=10.2.2
SiLA2.Authentication
User Authentication and Role-Based Authorization for SiLA2 .NET Implementation
| NuGet Package | SiLA2.Authentication on NuGet.org |
| Repository | https://gitlab.com/SiLA2/sila_csharp |
| SiLA Standard | https://sila-standard.com |
| License | MIT |
Overview
SiLA2.Authentication is an optional module for the SiLA2 .NET implementation that provides secure user authentication and role-based authorization for laboratory automation servers. This module enables SiLA2 servers to authenticate users, manage user accounts, and control access to features based on user roles.
The library is designed with a database-agnostic architecture, providing interface abstractions that can be implemented for any storage technology (SQL Server, PostgreSQL, MongoDB, etc.) while including a production-ready SQLite implementation out of the box.
Key Features
- Database-Agnostic Design - Interface-based architecture supports any storage backend
- Default SQLite Implementation - Production-ready implementation with Entity Framework Core
- BCrypt Password Hashing - Industry-standard password security (never stores plain text)
- Role-Based Authorization - Admin, Standard, and Undefined user roles
- User Management - Complete CRUD operations for user accounts
- Automatic Database Migration - DbUp-based migration system with embedded SQL scripts
- Default User Seeding - Pre-configured admin and user accounts for development
- Server-Specific Authentication - Validate users against specific server instances
- Dependency Injection Ready - Seamless ASP.NET Core integration
Relationship to SiLA2.Core
SiLA2.Authentication is an optional module that extends the base SiLA2 server functionality. While SiLA2.Core provides the fundamental server infrastructure, this module adds authentication and authorization capabilities for secure laboratory workflows.
When to use this module:
- Multi-user laboratory environments requiring access control
- Compliance requirements (FDA 21 CFR Part 11, EU Annex 11)
- Production deployments where user accountability is needed
- Systems requiring different permission levels (admin vs. standard users)
Installation
Install via NuGet Package Manager:
dotnet add package SiLA2.Authentication
Or via Package Manager Console:
Install-Package SiLA2.Authentication
Prerequisites
- .NET 10.0 or later
- SiLA2.Utils (automatically installed as dependency)
- Microsoft.EntityFrameworkCore 10.0.2+ (for SQLite implementation)
- dbup-sqlite 6.0.4+ (for database migrations)
Quick Start
Get authentication working in your SiLA2 server in 3 steps:
1. Add the Package
dotnet add package SiLA2.Authentication
2. Register Services in Program.cs
using SiLA2.Authentication.Extensions;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// Register SiLA2 Authentication with default SQLite implementation
builder.Services.AddSiLA2Authentication(options =>
{
options.UseSqlite("Data Source=users.db");
});
// Register other SiLA2 services...
builder.Services.AddSingleton<ISiLA2Server, SiLA2Server>();
builder.Services.AddGrpc();
var app = builder.Build();
3. Initialize Database on Startup
// Initialize authentication database (creates tables and seeds default users)
app.Services.EnsureAuthenticationDatabaseCreated();
// Map gRPC services and start server...
app.MapGrpcService<MySiLA2Service>();
app.Run();
4. Use in Your Services
using SiLA2.Authentication.Interfaces;
using SiLA2.Authentication.Models;
public class MyFeatureService : MyFeature.MyFeatureBase
{
private readonly IAuthenticationService _authService;
private readonly IUserManager _userManager;
public MyFeatureService(
IAuthenticationService authService,
IUserManager userManager)
{
_authService = authService;
_userManager = userManager;
}
public override async Task<Response> SecureCommand(
Parameters request,
ServerCallContext context)
{
// Authenticate user from request metadata
var username = context.GetHttpContext()?.User?.Identity?.Name ?? "anonymous";
var password = ExtractPasswordFromMetadata(context.RequestHeaders);
var result = await _authService.AuthenticateAsync(username, password);
if (!result.IsAuthenticated)
{
throw new RpcException(new Status(
StatusCode.Unauthenticated,
"Invalid credentials"));
}
// Check user role
if (result.User.Role != Role.Admin)
{
throw new RpcException(new Status(
StatusCode.PermissionDenied,
"Admin access required"));
}
// Execute command logic...
return new Response();
}
}
That's it! You now have a fully functional authentication system with:
- Admin account (login: "Admin", password: "Admin")
- Standard user account (login: "User", password: "User")
- SQLite database with proper password hashing
Security Warning: Default credentials are for development only. Change them immediately in production!
Core Components
Interfaces
The module provides a clean separation between interface contracts and implementations:
IAuthenticationService
Core authentication operations for login and password management.
public interface IAuthenticationService
{
// Basic authentication
Task<AuthenticationResult> AuthenticateAsync(
string login,
string password,
CancellationToken cancellationToken = default);
// Server-specific authentication
Task<AuthenticationResult> AuthenticateForServerAsync(
string login,
string password,
Guid requestedServerId,
Guid serverConfigId,
CancellationToken cancellationToken = default);
// Password operations
bool ValidatePassword(string passwordHash, string password);
string HashPassword(string password);
Task<bool> ChangePasswordAsync(
Guid userId,
string currentPassword,
string newPassword,
CancellationToken cancellationToken = default);
}
IUserManager
High-level user management combining repository and authentication functionality.
public interface IUserManager
{
// User CRUD operations
Task<User> CreateUserAsync(string login, string password, Role role = Role.Standard, CancellationToken cancellationToken = default);
Task<User> GetUserByIdAsync(Guid userId, CancellationToken cancellationToken = default);
Task<User> GetUserByLoginAsync(string login, CancellationToken cancellationToken = default);
Task<IEnumerable<User>> GetAllUsersAsync(CancellationToken cancellationToken = default);
Task<User> UpdateUserAsync(User user, CancellationToken cancellationToken = default);
Task DeleteUserAsync(Guid userId, CancellationToken cancellationToken = default);
// Role management
Task<User> UpdateUserRoleAsync(Guid userId, Role newRole, CancellationToken cancellationToken = default);
// Password management
Task<bool> UpdateUserPasswordAsync(Guid userId, string currentPassword, string newPassword, CancellationToken cancellationToken = default);
// Queries
Task<bool> UserExistsAsync(string login, CancellationToken cancellationToken = default);
IQueryable<User> GetUsers();
}
IUserRepository
Database-agnostic interface for user data access. Implement this interface to use custom storage backends.
public interface IUserRepository
{
Task<User> GetByIdAsync(Guid userId, CancellationToken cancellationToken = default);
Task<User> GetByLoginAsync(string login, CancellationToken cancellationToken = default);
Task<IEnumerable<User>> GetAllAsync(CancellationToken cancellationToken = default);
IQueryable<User> GetQueryable();
Task<User> CreateAsync(User user, CancellationToken cancellationToken = default);
Task<User> UpdateAsync(User user, CancellationToken cancellationToken = default);
Task DeleteAsync(Guid userId, CancellationToken cancellationToken = default);
Task<bool> ExistsAsync(string login, CancellationToken cancellationToken = default);
}
IAuthenticationInspector
Validates authentication requests with server identity checking.
public interface IAuthenticationInspector
{
Task<bool> IsAuthenticatedAsync(
string userIdentification,
string password,
Guid requestedServerId,
CancellationToken cancellationToken = default);
}
Models
User
Represents a user account with credentials and metadata.
public class User
{
public Guid Id { get; set; }
public string Login { get; set; }
public string PasswordHash { get; set; } // Never plain text!
public Role Role { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
Role
User roles for authorization.
[Flags]
public enum Role
{
Admin = 0, // Full access
Standard = 1, // Limited access
Undefined = 2 // No defined role
}
AuthenticationResult
Result of an authentication attempt.
public class AuthenticationResult
{
public bool IsAuthenticated { get; set; }
public User User { get; set; }
public string ErrorMessage { get; set; }
public AuthenticationFailureReason FailureReason { get; set; }
public static AuthenticationResult Success(User user);
public static AuthenticationResult Failure(AuthenticationFailureReason reason, string message);
}
public enum AuthenticationFailureReason
{
None,
UserNotFound,
InvalidPassword,
InvalidServer,
InternalError
}
Default Implementation
The module includes a production-ready SQLite implementation:
- SqliteAuthenticationService - BCrypt-based authentication
- SqliteUserRepository - Entity Framework Core repository
- UserManager - High-level user management
- AuthenticationInspector - Server identity validation
Dependency Injection Setup
Default SQLite Setup
The simplest setup uses the default SQLite implementation:
using SiLA2.Authentication.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Register authentication with default SQLite database
builder.Services.AddSiLA2Authentication(options =>
{
options.UseSqlite("Data Source=users.db");
});
var app = builder.Build();
// Initialize database (creates tables, runs migrations, seeds default users)
app.Services.EnsureAuthenticationDatabaseCreated();
app.Run();
SQL Server Configuration
For enterprise deployments with SQL Server:
using SiLA2.Authentication.Extensions;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// Register authentication with SQL Server
builder.Services.AddSiLA2Authentication(options =>
{
options.UseSqlServer(
builder.Configuration.GetConnectionString("AuthenticationDatabase"),
sqlOptions => sqlOptions.EnableRetryOnFailure()
);
});
var app = builder.Build();
app.Services.EnsureAuthenticationDatabaseCreated();
app.Run();
Connection String (appsettings.json):
{
"ConnectionStrings": {
"AuthenticationDatabase": "Server=localhost;Database=SiLA2Auth;Trusted_Connection=True;TrustServerCertificate=True;"
}
}
PostgreSQL Configuration
For PostgreSQL deployments:
builder.Services.AddSiLA2Authentication(options =>
{
options.UseNpgsql(
builder.Configuration.GetConnectionString("AuthenticationDatabase"),
pgOptions => pgOptions.EnableRetryOnFailure()
);
});
Connection String:
{
"ConnectionStrings": {
"AuthenticationDatabase": "Host=localhost;Database=sila2auth;Username=postgres;Password=yourpassword"
}
}
Add NuGet Package:
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
Custom Implementation Pattern
Implement your own storage backend for MongoDB, Azure Cosmos DB, or any other technology:
using SiLA2.Authentication.Interfaces;
using SiLA2.Authentication.Models;
// 1. Implement IUserRepository for your storage technology
public class MongoDbUserRepository : IUserRepository
{
private readonly IMongoCollection<User> _users;
public MongoDbUserRepository(IMongoDatabase database)
{
_users = database.GetCollection<User>("users");
}
public async Task<User> GetByLoginAsync(string login, CancellationToken cancellationToken = default)
{
return await _users.Find(u => u.Login == login).FirstOrDefaultAsync(cancellationToken);
}
// Implement other methods...
}
// 2. Implement IAuthenticationService (or reuse SqliteAuthenticationService)
public class MongoDbAuthenticationService : IAuthenticationService
{
private readonly IUserRepository _userRepository;
private readonly PasswordHashService _passwordHashService;
// Implement authentication logic...
}
// 3. Register custom implementations
builder.Services.AddSiLA2AuthenticationWithCustomImplementations<
MongoDbUserRepository,
MongoDbAuthenticationService>();
Usage Examples
Creating Users
using SiLA2.Authentication.Interfaces;
using SiLA2.Authentication.Models;
public class UserSetupService
{
private readonly IUserManager _userManager;
public UserSetupService(IUserManager userManager)
{
_userManager = userManager;
}
public async Task CreateLabUsers()
{
// Create admin user
var admin = await _userManager.CreateUserAsync(
login: "lab.admin@company.com",
password: "SecurePassword123!",
role: Role.Admin
);
Console.WriteLine($"Created admin: {admin.Login} (ID: {admin.Id})");
// Create standard user
var operator1 = await _userManager.CreateUserAsync(
login: "operator1@company.com",
password: "OperatorPass456!",
role: Role.Standard
);
Console.WriteLine($"Created operator: {operator1.Login}");
// Check if user already exists
if (await _userManager.UserExistsAsync("operator2@company.com"))
{
Console.WriteLine("User operator2@company.com already exists");
}
}
}
Authenticating Users
using SiLA2.Authentication.Interfaces;
using SiLA2.Authentication.Models;
public class LoginService
{
private readonly IAuthenticationService _authService;
public LoginService(IAuthenticationService authService)
{
_authService = authService;
}
public async Task<bool> Login(string username, string password)
{
var result = await _authService.AuthenticateAsync(username, password);
if (result.IsAuthenticated)
{
Console.WriteLine($"Welcome, {result.User.Login}!");
Console.WriteLine($"Role: {result.User.Role}");
return true;
}
switch (result.FailureReason)
{
case AuthenticationFailureReason.UserNotFound:
Console.WriteLine("User not found");
break;
case AuthenticationFailureReason.InvalidPassword:
Console.WriteLine("Invalid password");
break;
case AuthenticationFailureReason.InternalError:
Console.WriteLine($"Error: {result.ErrorMessage}");
break;
}
return false;
}
}
Managing User Roles
public class RoleManagementService
{
private readonly IUserManager _userManager;
public RoleManagementService(IUserManager userManager)
{
_userManager = userManager;
}
public async Task PromoteToAdmin(string login)
{
var user = await _userManager.GetUserByLoginAsync(login);
if (user == null)
{
Console.WriteLine($"User {login} not found");
return;
}
await _userManager.UpdateUserRoleAsync(user.Id, Role.Admin);
Console.WriteLine($"User {login} promoted to Admin");
}
public async Task DemoteToStandard(Guid userId)
{
await _userManager.UpdateUserRoleAsync(userId, Role.Standard);
Console.WriteLine("User demoted to Standard role");
}
public async Task ListUsersByRole()
{
var users = await _userManager.GetAllUsersAsync();
var admins = users.Where(u => u.Role == Role.Admin);
var standardUsers = users.Where(u => u.Role == Role.Standard);
Console.WriteLine("Administrators:");
foreach (var admin in admins)
{
Console.WriteLine($" - {admin.Login}");
}
Console.WriteLine("\nStandard Users:");
foreach (var user in standardUsers)
{
Console.WriteLine($" - {user.Login}");
}
}
}
Password Changes
public class PasswordManagementService
{
private readonly IUserManager _userManager;
public PasswordManagementService(IUserManager userManager)
{
_userManager = userManager;
}
public async Task<bool> ChangeUserPassword(
Guid userId,
string currentPassword,
string newPassword)
{
// Validate new password strength
if (newPassword.Length < 8)
{
Console.WriteLine("Password must be at least 8 characters");
return false;
}
var success = await _userManager.UpdateUserPasswordAsync(
userId,
currentPassword,
newPassword
);
if (success)
{
Console.WriteLine("Password changed successfully");
}
else
{
Console.WriteLine("Password change failed (invalid current password)");
}
return success;
}
public async Task ResetPassword(string login, string newPassword)
{
var user = await _userManager.GetUserByLoginAsync(login);
if (user == null)
{
Console.WriteLine($"User {login} not found");
return;
}
// Admin reset - update password hash directly
var authService = new SqliteAuthenticationService(
userRepository,
logger
);
user.SetPasswordHash(authService.HashPassword(newPassword));
await _userManager.UpdateUserAsync(user);
Console.WriteLine($"Password reset for {login}");
}
}
User Queries
public class UserQueryService
{
private readonly IUserManager _userManager;
public UserQueryService(IUserManager userManager)
{
_userManager = userManager;
}
public async Task QueryUsers()
{
// Get all users
var allUsers = await _userManager.GetAllUsersAsync();
Console.WriteLine($"Total users: {allUsers.Count()}");
// Find specific user
var user = await _userManager.GetUserByLoginAsync("admin");
if (user != null)
{
Console.WriteLine($"Found user: {user.Login}, Role: {user.Role}");
}
// Advanced queries using IQueryable
var recentUsers = _userManager.GetUsers()
.Where(u => u.CreatedAt > DateTime.UtcNow.AddDays(-7))
.OrderByDescending(u => u.CreatedAt)
.ToList();
Console.WriteLine($"\nUsers created in last 7 days: {recentUsers.Count}");
foreach (var recentUser in recentUsers)
{
Console.WriteLine($" - {recentUser.Login} (created {recentUser.CreatedAt:yyyy-MM-dd})");
}
}
}
Integration with SiLA2 Servers
Complete Server Example
using SiLA2.AspNetCore;
using SiLA2.Server;
using SiLA2.Authentication.Extensions;
using Microsoft.EntityFrameworkCore;
using MyFeatures.Services;
var builder = WebApplication.CreateBuilder(args);
// Register SiLA2 core services
builder.Services.AddSingleton<ISiLA2Server, SiLA2Server>();
builder.Services.AddGrpc();
// Register authentication with SQLite
builder.Services.AddSiLA2Authentication(options =>
{
options.UseSqlite("Data Source=users.db");
});
// Register your feature services
builder.Services.AddSingleton<MyFeatureService>();
builder.Services.AddSingleton<SecureFeatureService>();
var app = builder.Build();
// Initialize SiLA2 features
var siLA2Server = app.Services.GetRequiredService<ISiLA2Server>();
app.InitializeSiLA2Features(siLA2Server);
// Initialize authentication database
app.Services.EnsureAuthenticationDatabaseCreated();
// Map gRPC services
app.MapGrpcService<SiLAService>();
app.MapGrpcService<MyFeatureService>();
app.MapGrpcService<SecureFeatureService>();
app.Run();
Feature Service with Authentication
using SiLA2.Authentication.Interfaces;
using SiLA2.Authentication.Models;
using Grpc.Core;
public class SecureFeatureService : SecureFeature.SecureFeatureBase
{
private readonly IAuthenticationService _authService;
private readonly Feature _siLA2Feature;
public SecureFeatureService(
ISiLA2Server siLA2Server,
IAuthenticationService authService)
{
_siLA2Feature = siLA2Server.ReadFeature(
Path.Combine("Features", "SecureFeature-v1_0.sila.xml"));
_authService = authService;
}
public override async Task<Response> ExecuteSecureCommand(
Parameters request,
ServerCallContext context)
{
// Extract credentials from metadata
var metadata = context.RequestHeaders;
var username = metadata.GetValue("username");
var password = metadata.GetValue("password");
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
{
throw new RpcException(new Status(
StatusCode.Unauthenticated,
"Missing credentials"));
}
// Authenticate user
var authResult = await _authService.AuthenticateAsync(username, password);
if (!authResult.IsAuthenticated)
{
throw new RpcException(new Status(
StatusCode.Unauthenticated,
$"Authentication failed: {authResult.ErrorMessage}"));
}
// Check authorization
if (authResult.User.Role != Role.Admin)
{
throw new RpcException(new Status(
StatusCode.PermissionDenied,
"Admin role required for this operation"));
}
// Execute secure command logic
Console.WriteLine($"User {authResult.User.Login} executed secure command");
return new Response
{
Message = new String { Value = "Command executed successfully" }
};
}
}
Client-Side Metadata Example
using Grpc.Core;
using Grpc.Net.Client;
// Create channel
var channel = GrpcChannel.ForAddress("https://localhost:50051");
var client = new SecureFeature.SecureFeatureClient(channel);
// Add authentication metadata
var metadata = new Metadata
{
{ "username", "admin" },
{ "password", "Admin" }
};
// Make authenticated call
try
{
var response = await client.ExecuteSecureCommandAsync(
new Parameters(),
metadata
);
Console.WriteLine($"Success: {response.Message.Value}");
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.Unauthenticated)
{
Console.WriteLine("Authentication failed");
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.PermissionDenied)
{
Console.WriteLine("Permission denied");
}
gRPC Interceptor Integration
For automatic authentication on every request, use an interceptor:
using Grpc.Core;
using Grpc.Core.Interceptors;
using SiLA2.Authentication.Interfaces;
public class AuthenticationInterceptor : Interceptor
{
private readonly IAuthenticationService _authService;
public AuthenticationInterceptor(IAuthenticationService authService)
{
_authService = authService;
}
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation)
{
// Extract credentials
var username = context.RequestHeaders.GetValue("username");
var password = context.RequestHeaders.GetValue("password");
if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password))
{
var result = await _authService.AuthenticateAsync(username, password);
if (!result.IsAuthenticated)
{
throw new RpcException(new Status(
StatusCode.Unauthenticated,
"Invalid credentials"));
}
// Store user in context for downstream services
context.GetHttpContext()?.Items.Add("User", result.User);
}
return await continuation(request, context);
}
}
// Register interceptor in Program.cs
builder.Services.AddGrpc(options =>
{
options.Interceptors.Add<AuthenticationInterceptor>();
});
Database Setup
SQLite (Default)
SQLite is the default storage backend and requires no external database server:
builder.Services.AddSiLA2Authentication(options =>
{
options.UseSqlite("Data Source=users.db");
});
// Database file will be created automatically at users.db
app.Services.EnsureAuthenticationDatabaseCreated();
Advantages:
- Zero configuration
- Single file database
- Perfect for development and embedded systems
- Cross-platform
SQL Server Configuration
1. Install EF Core Tools:
dotnet tool install --global dotnet-ef
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
2. Configure in Program.cs:
builder.Services.AddSiLA2Authentication(options =>
{
options.UseSqlServer(
builder.Configuration.GetConnectionString("AuthenticationDatabase"),
sqlOptions => sqlOptions.EnableRetryOnFailure(
maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromSeconds(30),
errorNumbersToAdd: null
)
);
});
3. Apply Migrations:
// Automatic migration on startup
app.Services.EnsureAuthenticationDatabaseCreated();
// Or use EF CLI manually
dotnet ef database update --context AuthenticationDbContext
Connection String (appsettings.json):
{
"ConnectionStrings": {
"AuthenticationDatabase": "Server=localhost;Database=SiLA2Auth;Integrated Security=true;TrustServerCertificate=true;"
}
}
PostgreSQL Configuration
1. Install PostgreSQL Provider:
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
2. Configure in Program.cs:
builder.Services.AddSiLA2Authentication(options =>
{
options.UseNpgsql(
builder.Configuration.GetConnectionString("AuthenticationDatabase"),
pgOptions => pgOptions.EnableRetryOnFailure()
);
});
3. Create Database:
-- In PostgreSQL (psql)
CREATE DATABASE sila2auth;
CREATE USER sila2user WITH PASSWORD 'yourpassword';
GRANT ALL PRIVILEGES ON DATABASE sila2auth TO sila2user;
Connection String:
{
"ConnectionStrings": {
"AuthenticationDatabase": "Host=localhost;Database=sila2auth;Username=sila2user;Password=yourpassword"
}
}
Migration Management with DbUp
The module uses DbUp for database migrations, which runs embedded SQL scripts automatically:
Embedded Migration Scripts:
001_CreateUserTable.sql- Creates User table with indexes002_SeedDefaultUsers.sql- Seeds admin and user accounts
Adding Custom Migrations:
- Create a new SQL file in
Migrationsfolder:
-- 003_AddEmailColumn.sql
ALTER TABLE "User" ADD COLUMN "Email" TEXT;
- Mark as embedded resource in
.csproj:
<ItemGroup>
<EmbeddedResource Include="Migrations/*.sql" />
</ItemGroup>
- DbUp will automatically detect and run the new migration on next startup.
Default Users
The module seeds two default user accounts for development:
| Login | Password | Role | GUID |
|---|---|---|---|
Admin |
Admin |
Admin | 66D6855A-CC4A-4BCD-B4AE-10CE3C59F1BD |
User |
User |
Standard | 59845510-06DF-4981-AA68-253400D3090C |
Security Warning:
These default credentials are for development and testing only. They use well-known passwords that are included in embedded SQL scripts.
In production environments, you MUST:
- Change default passwords immediately after first deployment
- Delete default accounts and create secure user accounts
- Use strong passwords (minimum 12 characters, mixed case, numbers, special characters)
- Consider external authentication providers (OAuth, LDAP, Active Directory)
- Enable audit logging for authentication events (see SiLA2.Audit module)
Changing Default Passwords:
var userManager = app.Services.GetRequiredService<IUserManager>();
// Change admin password
var admin = await userManager.GetUserByLoginAsync("Admin");
await userManager.UpdateUserPasswordAsync(
admin.Id,
"Admin", // old password
"SecureAdminPassword123!" // new password
);
// Or delete default accounts entirely
await userManager.DeleteUserAsync(admin.Id);
Advanced Usage
Custom Repository Implementation
Implement IUserRepository for any storage technology:
using SiLA2.Authentication.Interfaces;
using SiLA2.Authentication.Models;
using MongoDB.Driver;
public class MongoDbUserRepository : IUserRepository
{
private readonly IMongoCollection<User> _users;
public MongoDbUserRepository(IMongoDatabase database)
{
_users = database.GetCollection<User>("users");
}
public async Task<User> GetByIdAsync(Guid userId, CancellationToken cancellationToken = default)
{
return await _users.Find(u => u.Id == userId)
.FirstOrDefaultAsync(cancellationToken);
}
public async Task<User> GetByLoginAsync(string login, CancellationToken cancellationToken = default)
{
return await _users.Find(u => u.Login == login)
.FirstOrDefaultAsync(cancellationToken);
}
public async Task<IEnumerable<User>> GetAllAsync(CancellationToken cancellationToken = default)
{
return await _users.Find(_ => true).ToListAsync(cancellationToken);
}
public IQueryable<User> GetQueryable()
{
return _users.AsQueryable();
}
public async Task<User> CreateAsync(User user, CancellationToken cancellationToken = default)
{
await _users.InsertOneAsync(user, cancellationToken: cancellationToken);
return user;
}
public async Task<User> UpdateAsync(User user, CancellationToken cancellationToken = default)
{
await _users.ReplaceOneAsync(u => u.Id == user.Id, user, cancellationToken: cancellationToken);
return user;
}
public async Task DeleteAsync(Guid userId, CancellationToken cancellationToken = default)
{
await _users.DeleteOneAsync(u => u.Id == userId, cancellationToken);
}
public async Task<bool> ExistsAsync(string login, CancellationToken cancellationToken = default)
{
var count = await _users.CountDocumentsAsync(u => u.Login == login, cancellationToken: cancellationToken);
return count > 0;
}
}
// Register in DI
builder.Services.AddSingleton<IMongoDatabase>(sp =>
{
var client = new MongoClient("mongodb://localhost:27017");
return client.GetDatabase("sila2auth");
});
builder.Services.AddSiLA2AuthenticationWithCustomImplementations<
MongoDbUserRepository,
SqliteAuthenticationService>(); // Reuse authentication logic
Custom Authentication Service
Implement custom authentication logic (e.g., LDAP, OAuth):
using SiLA2.Authentication.Interfaces;
using SiLA2.Authentication.Models;
public class LdapAuthenticationService : IAuthenticationService
{
private readonly IUserRepository _userRepository;
private readonly ILdapConnection _ldapConnection;
public LdapAuthenticationService(
IUserRepository userRepository,
ILdapConnection ldapConnection)
{
_userRepository = userRepository;
_ldapConnection = ldapConnection;
}
public async Task<AuthenticationResult> AuthenticateAsync(
string login,
string password,
CancellationToken cancellationToken = default)
{
// Authenticate against LDAP
bool isLdapValid = await _ldapConnection.ValidateCredentialsAsync(login, password);
if (!isLdapValid)
{
return AuthenticationResult.Failure(
AuthenticationFailureReason.InvalidPassword,
"LDAP authentication failed");
}
// Get or create user in local database
var user = await _userRepository.GetByLoginAsync(login, cancellationToken);
if (user == null)
{
// Auto-provision user from LDAP
user = new User(login, string.Empty, Role.Standard);
user = await _userRepository.CreateAsync(user, cancellationToken);
}
return AuthenticationResult.Success(user);
}
// Implement other methods...
}
Integration with External Authentication Providers
using Microsoft.AspNetCore.Authentication.JwtBearer;
using SiLA2.Authentication.Interfaces;
public class JwtAuthenticationService : IAuthenticationService
{
private readonly IUserRepository _userRepository;
private readonly IConfiguration _configuration;
public async Task<AuthenticationResult> AuthenticateAsync(
string login,
string jwtToken, // Treat password parameter as JWT token
CancellationToken cancellationToken = default)
{
// Validate JWT token
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = _configuration["Jwt:Issuer"],
ValidAudience = _configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]))
};
try
{
var principal = tokenHandler.ValidateToken(
jwtToken,
validationParameters,
out var validatedToken);
// Extract user info from claims
var userClaim = principal.FindFirst(ClaimTypes.Name)?.Value;
if (string.IsNullOrEmpty(userClaim))
{
return AuthenticationResult.Failure(
AuthenticationFailureReason.InvalidPassword,
"Invalid token claims");
}
// Get or create user
var user = await _userRepository.GetByLoginAsync(userClaim, cancellationToken);
return AuthenticationResult.Success(user);
}
catch (Exception)
{
return AuthenticationResult.Failure(
AuthenticationFailureReason.InvalidPassword,
"JWT validation failed");
}
}
}
API Reference
Key Interface Methods
IAuthenticationService
// Basic authentication
Task<AuthenticationResult> AuthenticateAsync(
string login,
string password,
CancellationToken cancellationToken = default)
// Server-specific authentication
Task<AuthenticationResult> AuthenticateForServerAsync(
string login,
string password,
Guid requestedServerId,
Guid serverConfigId,
CancellationToken cancellationToken = default)
// Password management
bool ValidatePassword(string passwordHash, string password)
string HashPassword(string password)
Task<bool> ChangePasswordAsync(
Guid userId,
string currentPassword,
string newPassword,
CancellationToken cancellationToken = default)
IUserManager
// User creation and retrieval
Task<User> CreateUserAsync(string login, string password, Role role = Role.Standard, CancellationToken cancellationToken = default)
Task<User> GetUserByIdAsync(Guid userId, CancellationToken cancellationToken = default)
Task<User> GetUserByLoginAsync(string login, CancellationToken cancellationToken = default)
Task<IEnumerable<User>> GetAllUsersAsync(CancellationToken cancellationToken = default)
// User updates
Task<User> UpdateUserAsync(User user, CancellationToken cancellationToken = default)
Task<User> UpdateUserRoleAsync(Guid userId, Role newRole, CancellationToken cancellationToken = default)
Task<bool> UpdateUserPasswordAsync(Guid userId, string currentPassword, string newPassword, CancellationToken cancellationToken = default)
// User deletion and queries
Task DeleteUserAsync(Guid userId, CancellationToken cancellationToken = default)
Task<bool> UserExistsAsync(string login, CancellationToken cancellationToken = default)
IQueryable<User> GetUsers()
Authentication Workflow
Client Request
|
v
[Extract Credentials from Metadata]
|
v
[IAuthenticationService.AuthenticateAsync()]
|
v
[IUserRepository.GetByLoginAsync()]
|
v
[PasswordHashService.Verify(hash, password)]
|
+--- Invalid --> AuthenticationResult.Failure()
|
+--- Valid --> AuthenticationResult.Success(user)
|
v
[Check Role]
|
+--- Admin --> Allow Full Access
|
+--- Standard --> Allow Limited Access
|
+--- Undefined --> Deny Access
Dependencies
SiLA2.Authentication has the following NuGet dependencies:
| Package | Version | Purpose |
|---|---|---|
dbup-sqlite |
6.0.4 | Database migration management |
Microsoft.EntityFrameworkCore |
10.0.2 | Data access abstraction layer |
Microsoft.EntityFrameworkCore.Sqlite |
10.0.2 | SQLite database provider |
SiLA2.Utils |
10.2.2+ | SiLA2 utility library (password hashing) |
Additional dependencies for other databases:
| Database | Package |
|---|---|
| SQL Server | Microsoft.EntityFrameworkCore.SqlServer 10.0.2+ |
| PostgreSQL | Npgsql.EntityFrameworkCore.PostgreSQL 10.0.0+ |
| MySQL | Pomelo.EntityFrameworkCore.MySql 10.0.0+ |
Platform Support
- Target Framework: .NET 10.0
- Operating Systems: Windows, Linux, macOS
- Architecture: Platform-independent (AnyCPU)
- Embedded Systems: Fully supported (SQLite is ideal for embedded deployments)
Troubleshooting
Issue: Database File Not Created
Symptom: SQLite database file doesn't exist after calling EnsureAuthenticationDatabaseCreated()
Solution:
// Check if database creation was called
var migrated = app.Services.EnsureAuthenticationDatabaseCreated();
Console.WriteLine($"Database migrated: {migrated}");
// Verify connection string
var dbContext = app.Services.GetRequiredService<AuthenticationDbContext>();
Console.WriteLine($"Connection string: {dbContext.Database.GetConnectionString()}");
// Ensure directory exists
var dbPath = "users.db";
var directory = Path.GetDirectoryName(Path.GetFullPath(dbPath));
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
Issue: Default Users Not Seeded
Symptom: Cannot login with "Admin" or "User" default accounts
Solution:
// Check if users exist
var userManager = app.Services.GetRequiredService<IUserManager>();
var admin = await userManager.GetUserByLoginAsync("Admin");
if (admin == null)
{
Console.WriteLine("Admin user not found - migration may have failed");
// Manually create admin user
await userManager.CreateUserAsync("Admin", "Admin", Role.Admin);
}
// Verify migration scripts are embedded
var assembly = typeof(ServiceCollectionExtensions).Assembly;
var resources = assembly.GetManifestResourceNames();
foreach (var resource in resources)
{
Console.WriteLine($"Embedded resource: {resource}");
}
Issue: Authentication Always Fails
Symptom: Valid credentials are rejected
Solution:
// Enable detailed logging
builder.Logging.SetMinimumLevel(LogLevel.Debug);
builder.Logging.AddConsole();
// Test password hashing
var authService = app.Services.GetRequiredService<IAuthenticationService>();
var hash = authService.HashPassword("Admin");
Console.WriteLine($"Password hash: {hash}");
var isValid = authService.ValidatePassword(hash, "Admin");
Console.WriteLine($"Password validation: {isValid}");
// Check user in database
var userManager = app.Services.GetRequiredService<IUserManager>();
var user = await userManager.GetUserByLoginAsync("Admin");
if (user != null)
{
Console.WriteLine($"User found: {user.Login}, Hash: {user.PasswordHash}");
}
Issue: Migration Errors with SQL Server
Symptom: EnsureAuthenticationDatabaseCreated() throws exception with SQL Server
Solution:
DbUp migrations are currently designed for SQLite. For SQL Server, use Entity Framework migrations instead:
# Add migration
dotnet ef migrations add InitialCreate --context AuthenticationDbContext
# Apply migration
dotnet ef database update --context AuthenticationDbContext
Or manually create tables:
CREATE TABLE [User] (
[Id] UNIQUEIDENTIFIER PRIMARY KEY NOT NULL,
[Login] NVARCHAR(255) NOT NULL UNIQUE,
[PasswordHash] NVARCHAR(MAX) NOT NULL,
[Role] INT NOT NULL DEFAULT 1,
[CreatedAt] DATETIME2 NOT NULL DEFAULT GETUTCDATE(),
[UpdatedAt] DATETIME2 NOT NULL DEFAULT GETUTCDATE()
);
CREATE INDEX IX_User_Login ON [User] ([Login]);
Issue: Connection String Not Found
Symptom: Exception: "A relational database connection string was not found"
Solution:
// Verify appsettings.json is copied to output
// Check .csproj has:
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
// Or configure connection string in code
builder.Services.AddSiLA2Authentication(options =>
{
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
?? "Data Source=users.db";
options.UseSqlite(connectionString);
});
Security Best Practices
Password Security
- Passwords are never stored in plain text - always hashed with BCrypt
- BCrypt includes automatic salt generation (unique per password)
- Hash format:
{salt};{hash}(Base64 encoded) - Verification is constant-time to prevent timing attacks
Secure Storage
// ✅ GOOD: Use secure password
var user = await userManager.CreateUserAsync(
"admin@company.com",
"MySecure!Password123", // Strong password
Role.Admin
);
// ❌ BAD: Weak password
var user = await userManager.CreateUserAsync(
"admin",
"admin", // Easily guessed
Role.Admin
);
Connection String Security
// ✅ GOOD: Store in user secrets or environment variables
builder.Configuration.AddUserSecrets<Program>();
var connectionString = builder.Configuration.GetConnectionString("AuthenticationDatabase");
// ❌ BAD: Hardcoded credentials
var connectionString = "Server=localhost;Database=SiLA2Auth;User=sa;Password=password123";
Production Checklist
- Change all default passwords
- Delete or disable default user accounts
- Use strong password policy (12+ characters, mixed case, numbers, symbols)
- Store connection strings in secure configuration (Azure Key Vault, AWS Secrets Manager, user secrets)
- Enable HTTPS/TLS for all gRPC communication
- Use SQL Server or PostgreSQL (not SQLite) for production
- Enable database backups
- Integrate with SiLA2.Audit for authentication logging
- Consider external authentication (OAuth, LDAP, Active Directory)
- Implement account lockout after failed attempts
- Enable multi-factor authentication (MFA) if required
Additional Resources
- SiLA Consortium: https://sila-standard.com
- SiLA2 C# Wiki: https://gitlab.com/SiLA2/sila_csharp/-/wikis/home
- Main Repository: https://gitlab.com/SiLA2/sila_csharp
- Issues & Feature Requests: https://gitlab.com/SiLA2/sila_csharp/-/issues
- Example Implementation:
src/Examples/AuthenticationAuthorization/
Related Modules
- SiLA2.Audit - Track authentication events and user actions for compliance
- SiLA2.Utils - Core utilities including password hashing (PasswordHashService)
- SiLA2.Database.SQL - Additional SQL database utilities
External Documentation
- BCrypt Algorithm: https://en.wikipedia.org/wiki/Bcrypt
- Entity Framework Core: https://docs.microsoft.com/ef/core/
- DbUp Migrations: https://dbup.readthedocs.io/
- gRPC Authentication: https://grpc.io/docs/guides/auth/
Contributing
This library is part of the SiLA2 C# implementation. Contributions are welcome!
- Fork the repository
- Create a feature branch
- Make your changes
- Add unit tests
- Submit a merge request
For issues and feature requests, visit: https://gitlab.com/SiLA2/sila_csharp/-/issues
License
This project is licensed under the MIT License. See the repository 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
- dbup-sqlite (>= 6.0.4)
- Microsoft.EntityFrameworkCore (>= 10.0.3)
- Microsoft.EntityFrameworkCore.Sqlite (>= 10.0.3)
- SiLA2.Utils (>= 10.2.2)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on SiLA2.Authentication:
| Package | Downloads |
|---|---|
|
SiLA2.Frontend.Razor
Web Frontend Extension for SiLA2.Server Package |
GitHub repositories
This package is not used by any popular GitHub repositories.